/* * Copyright (c) 2008-2012 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.placement; import static com.emc.storageos.api.mapper.TaskMapper.toTask; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; import com.emc.storageos.api.service.impl.resource.AbstractBlockServiceApiImpl; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.customconfigcontroller.CustomConfigConstants; import com.emc.storageos.customconfigcontroller.impl.CustomConfigHandler; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.ContainmentPrefixConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.AutoTieringPolicy; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockMirror; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.Cluster; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.DiscoveredDataObject.Type; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.ExportGroup.ExportGroupType; import com.emc.storageos.db.client.model.Host; import com.emc.storageos.db.client.model.NamedURI; import com.emc.storageos.db.client.model.OpStatusMap; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.StoragePool; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringMap; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.db.client.model.SynchronizationState; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.model.VirtualPool; import com.emc.storageos.db.client.model.VirtualPool.FileReplicationType; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.VolumeGroup; import com.emc.storageos.db.client.model.VpoolRemoteCopyProtectionSettings; import com.emc.storageos.db.client.util.CommonTransformerFunctions; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.TaskList; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.volumecontroller.AttributeMatcher; import com.emc.storageos.volumecontroller.AttributeMatcher.Attributes; import com.emc.storageos.volumecontroller.Recommendation; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.plugins.metering.smis.processor.PortMetricsProcessor; import com.emc.storageos.volumecontroller.impl.utils.AttributeMapBuilder; import com.emc.storageos.volumecontroller.impl.utils.AttributeMatcherFramework; import com.emc.storageos.volumecontroller.impl.utils.ObjectLocalCache; import com.emc.storageos.volumecontroller.impl.utils.ProvisioningAttributeMapBuilder; import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper; import com.emc.storageos.volumecontroller.impl.utils.attrmatchers.CapacityMatcher; import com.emc.storageos.volumecontroller.impl.utils.attrmatchers.MaxResourcesMatcher; import com.google.common.base.Joiner; import com.google.common.collect.Collections2; /** * Basic storage scheduling functions of block and file storage. StorageScheduler is done based on desired * virtual pool parameters for the provisioned storage. */ public class StorageScheduler implements Scheduler { public static final Logger _log = LoggerFactory.getLogger(StorageScheduler.class); private static final String SCHEDULER_NAME = "block"; // factor to adjust weight of array depending on export type // for host export, shared arrays (host has shared volumes on those arrays) will have less weight // for cluster export, exclusive arrays (those have only exclusive volumes to the hosts in the cluster), will have less weight // if the factor is 0, then shared arrays will not be treated as preferred arrays for host export, // and exclusive arrays will not be treated as preferred arrays for cluster export private static double AFFINITY_FACTOR = 0.999; private DbClient _dbClient; private CoordinatorClient _coordinator; private CustomConfigHandler _customConfigHandler; private PortMetricsProcessor _portMetricsProcessor; private final Comparator<StoragePool> _storagePoolComparator = new StoragePoolDefaultComparator(); private AttributeMatcherFramework _matcherFramework; public void setDbClient(DbClient dbClient) { _dbClient = dbClient; } public void setCoordinator(CoordinatorClient coordinator) { _coordinator = coordinator; } public void setCustomConfigHandler(CustomConfigHandler customConfigHandler) { _customConfigHandler = customConfigHandler; } public void setPortMetricsProcessor(PortMetricsProcessor portMetricsProcessor) { if (_portMetricsProcessor == null) { _portMetricsProcessor = portMetricsProcessor; } } public AttributeMatcherFramework getMatcherFramework() { return _matcherFramework; } public void setMatcherFramework(AttributeMatcherFramework matcherFramework) { _matcherFramework = matcherFramework; } public CoordinatorClient getCoordinator() { return _coordinator; } /** * Returns list of recommendations for block volumes. * * Select and return one or more storage pools where the volume(s)/fileshare(s) * should be created. The placement logic is based on: * - VirtualArray, only storage devices in the given varray are candidates * - VirtualPool, specifies must-meet & best-meet service specifications * - access-protocols: storage pools must support all protocols specified in VirtualPool * - snapshots: if yes, only select storage pools with this capability * - snapshot-consistency: if yes, only select storage pools with this capability * - performance: best-match, select storage pools which meets desired performance * - provision-mode: thick/thin * - numPaths: select storage pools with required number of paths to the volume * - size: Place the resources in the minimum number of storage pools that can * accommodate the size and number of resource requested. * * @param neighborhood * @param cos * @param capabilities * @return list of VolumeRecommendation instances */ @Override public List<Recommendation> getRecommendationsForResources(VirtualArray neighborhood, Project project, VirtualPool cos, VirtualPoolCapabilityValuesWrapper capabilities) { _log.debug("Schedule storage for {} resource(s) of size {}.", capabilities.getResourceCount(), capabilities.getSize()); List<Recommendation> volumeRecommendations = new ArrayList<Recommendation>(); // Initialize a list of recommendations to be returned. List<Recommendation> recommendations = new ArrayList<Recommendation>(); Map<String, Object> attributeMap = new HashMap<String, Object>(); // Get all storage pools that match the passed CoS params and // protocols. In addition, the pool must have enough capacity // to hold at least one resource of the requested size. List<StoragePool> candidatePools = getMatchingPools(neighborhood, cos, capabilities, attributeMap); if (CollectionUtils.isEmpty(candidatePools)) { StringBuffer errorMessage = new StringBuffer(); if (attributeMap.get(AttributeMatcher.ERROR_MESSAGE) != null) { errorMessage = (StringBuffer) attributeMap.get(AttributeMatcher.ERROR_MESSAGE); } throw APIException.badRequests.noStoragePools(neighborhood.getLabel(), cos.getLabel(), errorMessage.toString()); } // Get the recommendations for the candidate pools. recommendations = getRecommendationsForPools(neighborhood.getId().toString(), candidatePools, capabilities); // We need to place all the resources. If we can't then // log an error and clear the list of recommendations. if (recommendations.isEmpty()) { // TODO reevaluate _log.error( "Could not find matching pools for VArray {} & VPool {}", neighborhood.getId(), cos.getId()); return volumeRecommendations; } // create list of VolumeRecommendation(s) for volumes for (Recommendation recommendation : recommendations) { int count = recommendation.getResourceCount(); while (count > 0) { VolumeRecommendation volumeRecommendation = new VolumeRecommendation(VolumeRecommendation.VolumeType.BLOCK_VOLUME, capabilities.getSize(), cos, neighborhood.getId()); volumeRecommendation.setSourceStoragePool(recommendation.getSourceStoragePool()); volumeRecommendation.setSourceStorageSystem(recommendation.getSourceStorageSystem()); volumeRecommendation.setVirtualArray(neighborhood.getId()); volumeRecommendation.setVirtualPool(cos); volumeRecommendation.setResourceCount(1); volumeRecommendation.addStoragePool(recommendation.getSourceStoragePool()); volumeRecommendation.addStorageSystem(recommendation.getSourceStorageSystem()); volumeRecommendations.add(volumeRecommendation); if (capabilities.getBlockConsistencyGroup() != null) { volumeRecommendation.setParameter(VolumeRecommendation.ARRAY_CG, capabilities.getBlockConsistencyGroup()); } count--; } } return volumeRecommendations; } public void getRecommendationsForMirrors(VirtualArray vArray, VirtualPool vPool, VirtualPoolCapabilityValuesWrapper capabilities, List<Recommendation> volumeRecommendations) { List<VolumeRecommendation> mirrorRecommendations = new ArrayList<VolumeRecommendation>(); // separate volumes by different devices Map<URI, List<VolumeRecommendation>> deviceMap = VolumeRecommendation.getDeviceMap(volumeRecommendations, _dbClient); // get matching pools for recommendations on each device Set<URI> storageSystems = deviceMap.keySet(); for (URI storageSystem : storageSystems) { Map<String, Object> attributeMap = new HashMap<String, Object>(); Set<String> storageSystemSet = new HashSet<String>(); storageSystemSet.add(storageSystem.toString()); attributeMap.put(AttributeMatcher.Attributes.storage_system.name(), storageSystemSet); Set<String> virtualArraySet = new HashSet<String>(); virtualArraySet.add(vArray.getId().toString()); attributeMap.put(AttributeMatcher.Attributes.varrays.name(), virtualArraySet); _log.info("Matching pools for storage system {} ", storageSystem); List<StoragePool> matchedPools = getMatchingPools(vArray, vPool, capabilities, attributeMap); if (matchedPools == null || matchedPools.isEmpty()) { // TODO fix message and throw service code exception _log.warn("VArray {} does not have storage pools which match VPool {}.", vArray.getId(), vPool.getId()); StringBuffer errorMessage = new StringBuffer(); if (attributeMap.get(AttributeMatcher.ERROR_MESSAGE) != null) { errorMessage = (StringBuffer) attributeMap.get(AttributeMatcher.ERROR_MESSAGE); } throw APIException.badRequests.noStoragePools(vArray.getLabel(), vPool.getLabel(), errorMessage.toString()); } // place all mirrors for this storage system in the matched pools List<VolumeRecommendation> sourceVolumeRecommendations = deviceMap.get(storageSystem); for (VolumeRecommendation sourceRecommendation : sourceVolumeRecommendations) { StoragePool poolForMirror = placeLocalMirror(matchedPools, sourceRecommendation); // create mirror volume recommendation VolumeRecommendation mirrorRecommendation = new VolumeRecommendation(VolumeRecommendation.VolumeType.BLOCK_LOCAL_MIRROR, capabilities.getSize(), vPool, vArray.getId()); mirrorRecommendation.addStoragePool(poolForMirror.getId()); mirrorRecommendation.addStorageSystem(poolForMirror.getStorageDevice()); mirrorRecommendation.setParameter(VolumeRecommendation.BLOCK_VOLUME, sourceRecommendation); sourceRecommendation.setParameter(VolumeRecommendation.LOCAL_MIRROR, mirrorRecommendation); mirrorRecommendations.add(mirrorRecommendation); } } volumeRecommendations.addAll(mirrorRecommendations); } private StoragePool placeLocalMirror(List<StoragePool> candidatePools, VolumeRecommendation sourceVolumeRecommendation) { URI sourcePoolId = sourceVolumeRecommendation.getCandidatePools().get(0); _log.info("START blockmirror placement"); _log.debug("We have {} candidate pool(s)", candidatePools.size()); _log.debug("Source pool is: {}", sourcePoolId); sortPools(candidatePools); StoragePool poolForMirror = candidatePools.get(0); if (candidatePools.size() > 1) { // do not use storage pool which was used for source volume if (poolForMirror.getId().equals(sourcePoolId)) { poolForMirror = candidatePools.get(1); } } return poolForMirror; } public List<VolumeRecommendation> getRecommendationsForVolumeClones(VirtualArray vArray, VirtualPool vPool, BlockObject blockObject, VirtualPoolCapabilityValuesWrapper capabilities) { _log.debug("Schedule storage for {} block volume copies {} of size {}.", capabilities.getResourceCount(), capabilities.getSize()); List<VolumeRecommendation> volumeRecommendations = new ArrayList<VolumeRecommendation>(); // Initialize a list of recommendations to be returned. List<Recommendation> recommendations = new ArrayList<Recommendation>(); // For volume clones, candidate pools should be on the same storage system as the source volume URI storageSystemId = blockObject.getStorageController(); Map<String, Object> attributeMap = new HashMap<String, Object>(); Set<String> storageSystemSet = new HashSet<String>(); storageSystemSet.add(storageSystemId.toString()); attributeMap.put(AttributeMatcher.Attributes.storage_system.name(), storageSystemSet); Set<String> virtualArraySet = new HashSet<String>(); virtualArraySet.add(vArray.getId().toString()); attributeMap.put(AttributeMatcher.Attributes.varrays.name(), virtualArraySet); // Get all storage pools that match the passed CoS params and // protocols. In addition, the pool must have enough capacity // to hold at least one resource of the requested size. // In addition, we need to only select pools from the // StorageSystem that the source volume was created against. List<StoragePool> matchedPools = getMatchingPools(vArray, vPool, capabilities, attributeMap); if (matchedPools == null || matchedPools.isEmpty()) { StringBuffer errMes = new StringBuffer(); if (attributeMap.get(AttributeMatcher.ERROR_MESSAGE) != null) { errMes = (StringBuffer) attributeMap.get(AttributeMatcher.ERROR_MESSAGE); } _log.warn("VArray {} does not have storage pools which match VPool {} to clone volume {}. {}", new Object[] { vArray.getId(), vPool.getId(), blockObject.getId(), errMes }); throw APIException.badRequests.noMatchingStoragePoolsForVpoolAndVarrayForClones(vPool.getLabel(), vArray.getLabel(), blockObject.getId(), errMes.toString()); } _log.info(String.format("Found %s candidate pools for placement of %s clone(s) of volume %s .", String.valueOf(matchedPools.size()), String.valueOf(capabilities.getResourceCount()), blockObject.getId())); // Get the recommendations for the candidate pools. recommendations = getRecommendationsForPools(vArray.getId().toString(), matchedPools, capabilities); // We need to place all the resources. If we can't then // log an error and clear the list of recommendations. if (recommendations.isEmpty()) { String msg = String.format("Could not find placement for %s clones of volume %s with capacity %s", capabilities.getResourceCount(), blockObject.getId(), capabilities.getSize()); _log.error(msg); throw APIException.badRequests.invalidParameterNoStorageFoundForVolume(vArray.getId(), vPool.getId(), blockObject.getId()); } // create list of VolumeRecommendation(s) for volumes for (Recommendation recommendation : recommendations) { int count = recommendation.getResourceCount(); while (count > 0) { VolumeRecommendation volumeRecommendation = new VolumeRecommendation(VolumeRecommendation.VolumeType.BLOCK_COPY, capabilities.getSize(), vPool, vArray.getId()); volumeRecommendation.addStoragePool(recommendation.getSourceStoragePool()); volumeRecommendation.addStorageSystem(recommendation.getSourceStorageSystem()); volumeRecommendations.add(volumeRecommendation); if (capabilities.getBlockConsistencyGroup() != null) { volumeRecommendation.setParameter(VolumeRecommendation.ARRAY_CG, capabilities.getBlockConsistencyGroup()); } count--; } } return volumeRecommendations; } /** * Sort list of storage pools based on its storage system's average usage port metrics usage. Its * secondary sorting components are free and subscribed capacity * * @param storagePools */ public void sortPools(List<StoragePool> storagePools) { // compute and set storage pools'average port usage metrics before sorting. _portMetricsProcessor.computeStoragePoolsAvgPortMetrics(storagePools); /** * Sort all pools in ascending order of its storage system's average port usage metrics (first order), * descending order by free capacity (second order) and in ascending order by ratio * of pool's subscribed capacity to total capacity(suborder). * This order is kept through the selection procedure. */ Collections.sort(storagePools, _storagePoolComparator); } /** * Select candidate storage pools for placement. Wrapper for the * 4 parameter version (below), which uses an optional parameter * for passing in attributes. * * @param varray The VirtualArray for matching storage pools. * @param vpool The virtualPool that must be satisfied by the storage pool. * @param capabilities The VirtualPool params that must be satisfied. * * @return A list of matching storage pools. */ protected List<StoragePool> getMatchingPools(VirtualArray varray, VirtualPool vpool, VirtualPoolCapabilityValuesWrapper capabilities) { return getMatchingPools(varray, vpool, capabilities, null); } /** * Select candidate storage pools for placement. * * @param varray The VirtualArray for matching storage pools. * @param vpool The virtualPool that must be satisfied by the storage pool. * @param capabilities The VirtualPool params that must be satisfied. * @param optionalAttributes Optional addition attributes to consider for placement * * @return A list of matching storage pools. */ protected List<StoragePool> getMatchingPools(VirtualArray varray, VirtualPool vpool, VirtualPoolCapabilityValuesWrapper capabilities, Map<String, Object> optionalAttributes) { capabilities.put(VirtualPoolCapabilityValuesWrapper.VARRAYS, varray.getId().toString()); if (null != vpool.getAutoTierPolicyName()) { capabilities.put(VirtualPoolCapabilityValuesWrapper.AUTO_TIER__POLICY_NAME, vpool.getAutoTierPolicyName()); } List<StoragePool> storagePools = new ArrayList<StoragePool>(); String varrayId = varray.getId().toString(); // Verify that if the VirtualPool is assigned to one or more VirtualArrays // there is a match with the passed VirtualArray. StringSet vpoolVarrays = vpool.getVirtualArrays(); if ((vpoolVarrays != null) && (!vpoolVarrays.contains(varrayId))) { _log.error("VirtualPool {} is not in virtual array {}", vpool.getId(), varrayId); return storagePools; } // Get pools for VirtualPool and VirtualArray List<StoragePool> matchedPoolsForCos = VirtualPool.getValidStoragePools(vpool, _dbClient, true); if (matchedPoolsForCos.isEmpty()) { _log.warn("vPool {} does not have any valid storage pool in vArray {}.", vpool.getId(), varray.getId()); throw APIException.badRequests.noStoragePoolsForVpoolInVarray(varray.getLabel(), vpool.getLabel()); } AttributeMapBuilder provMapBuilder = new ProvisioningAttributeMapBuilder(capabilities.getSize(), varrayId, capabilities.getThinVolumePreAllocateSize()); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.provisioning_type.toString(), vpool.getSupportedProvisioningType()); // Set CG related attributes for placement final BlockConsistencyGroup consistencyGroup = capabilities.getBlockConsistencyGroup() == null ? null : _dbClient .queryObject(BlockConsistencyGroup.class, capabilities.getBlockConsistencyGroup()); if (consistencyGroup != null) { URI cgStorageSystemURI = consistencyGroup.getStorageController(); if (!NullColumnValueGetter.isNullURI(cgStorageSystemURI)) { StorageSystem cgStorageSystem = _dbClient.queryObject(StorageSystem.class, cgStorageSystemURI); Set<String> storageSystemSet = new HashSet<String>(); // if srdf, then select Storage System based on whether the matching Pools runs on source or target. // this logic is added, as we don't want to add any relationships between source and target CGs explicitly in ViPR. if (VirtualPoolCapabilityValuesWrapper.SRDF_TARGET.equalsIgnoreCase(capabilities.getPersonality())) { // then update storage system corresponding to target CG // source Label is set as alternate name for target Cgs, so that the same name can be used to create targte CGs in // Array. List<URI> cgUris = _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getBlockConsistencyGroupByAlternateNameConstraint(consistencyGroup.getLabel())); if (!cgUris.isEmpty()) { BlockConsistencyGroup targetCgGroup = _dbClient.queryObject(BlockConsistencyGroup.class, cgUris.get(0)); if (null != targetCgGroup && !targetCgGroup.getInactive() && null != targetCgGroup.getStorageController() && !NullColumnValueGetter.isNullURI(targetCgGroup.getStorageController())) { storageSystemSet.add(targetCgGroup.getStorageController().toString()); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.storage_system.name(), storageSystemSet); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.multi_volume_consistency.name(), true); } } } else if (!DiscoveredDataObject.Type.vplex.name().equals(cgStorageSystem.getSystemType())) { // If this is not a VPLEX CG, add the ConsistencyGroup's StorageSystem // so that the matching pools are in the same system storageSystemSet.add(cgStorageSystemURI.toString()); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.storage_system.name(), storageSystemSet); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.multi_volume_consistency.name(), true); // IBM XIV requires all volumes of a CG in the same StoragePool if (DiscoveredDataObject.Type.ibmxiv.name().equals(cgStorageSystem.getSystemType())) { List<Volume> activeCGVolumes = CustomQueryUtility .queryActiveResourcesByConstraint( _dbClient, Volume.class, AlternateIdConstraint.Factory .getBlockObjectsByConsistencyGroup(consistencyGroup .getId().toString())); if (!activeCGVolumes.isEmpty()) { URI cgPoolURI = activeCGVolumes.get(0).getPool(); if (!NullColumnValueGetter.isNullURI(cgPoolURI)) { Iterator<StoragePool> itr = matchedPoolsForCos.iterator(); while (itr.hasNext()) { // remove all pools from matchedPoolsForCos except the one matched URI poolURI = itr.next().getId(); if (!cgPoolURI.equals(poolURI)) { _log.debug("Remove pool {} from list", poolURI); itr.remove(); } } } if (!matchedPoolsForCos.isEmpty()) { _log.debug("Pool {} is used by CG {}", cgPoolURI, consistencyGroup.getId()); } else { _log.warn("vPool {} does not have required IBM XIV storage pool in vArray {}.", vpool.getId(), varray.getId()); throw APIException.badRequests.noStoragePoolsForVpoolInVarray(varray.getLabel(), vpool.getLabel()); } } } } else { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.multi_volume_consistency.name(), true); } } else { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.multi_volume_consistency.name(), true); } } // If Storage System specified in capabilities, set that value (which may override CG) in the attributes. // This is used by the VPLEX to control consistency groups. if (capabilities.getSourceStorageDevice() != null) { StorageSystem sourceStorageSystem = capabilities.getSourceStorageDevice(); Set<String> storageSystemSet = new HashSet<String>(); storageSystemSet.add(sourceStorageSystem.getId().toString()); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.storage_system.name(), storageSystemSet); } else if (capabilities.getExcludedStorageDevice() != null) { StorageSystem excludedStorageSystem = capabilities.getExcludedStorageDevice(); Set<String> storageSystemSet = new HashSet<String>(); storageSystemSet.add(excludedStorageSystem.getId().toString()); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.exclude_storage_system.name(), storageSystemSet); } // populate DriveType,and Raid level and Policy Name for FAST Initial Placement Selection provMapBuilder.putAttributeInMap(Attributes.auto_tiering_policy_name.toString(), vpool.getAutoTierPolicyName()); provMapBuilder.putAttributeInMap(Attributes.unique_policy_names.toString(), vpool.getUniquePolicyNames()); provMapBuilder.putAttributeInMap(AttributeMatcher.PLACEMENT_MATCHERS, true); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.drive_type.name(), vpool.getDriveType()); StringSetMap arrayInfo = vpool.getArrayInfo(); if (null != arrayInfo) { Set<String> raidLevels = arrayInfo.get(VirtualPoolCapabilityValuesWrapper.RAID_LEVEL); if (null != raidLevels && !raidLevels.isEmpty()) { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.raid_levels.name(), raidLevels); } Set<String> systemTypes = arrayInfo.get(AttributeMatcher.Attributes.system_type.name()); if (null != systemTypes && !systemTypes.isEmpty()) { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.system_type.name(), systemTypes); // put quota value for ecs storage if (systemTypes.contains("ecs") && capabilities.getQuota() != null) { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.quota.name(), capabilities.getQuota()); } } } Map<URI, VpoolRemoteCopyProtectionSettings> remoteProtectionSettings = vpool.getRemoteProtectionSettings(vpool, _dbClient); if (null != remoteProtectionSettings && !remoteProtectionSettings.isEmpty()) { provMapBuilder.putAttributeInMap(Attributes.remote_copy.toString(), VirtualPool.groupRemoteCopyModesByVPool(vpool.getId(), remoteProtectionSettings)); } if (VirtualPoolCapabilityValuesWrapper.FILE_REPLICATION_SOURCE.equalsIgnoreCase(capabilities.getPersonality())) { // Run the placement algorithm for file replication!!! if (capabilities.getFileReplicationType() != null && !FileReplicationType.NONE.name().equalsIgnoreCase(capabilities.getFileReplicationType())) { provMapBuilder.putAttributeInMap(Attributes.file_replication_type.toString(), capabilities.getFileReplicationType()); if (capabilities.getFileRpCopyMode() != null) { provMapBuilder.putAttributeInMap(Attributes.file_replication_copy_mode.toString(), capabilities.getFileRpCopyMode()); } if (capabilities.getFileReplicationTargetVArrays() != null) { provMapBuilder.putAttributeInMap(Attributes.file_replication_target_varray.toString(), capabilities.getFileReplicationTargetVArrays()); } if (capabilities.getFileReplicationTargetVPool() != null) { provMapBuilder.putAttributeInMap(Attributes.file_replication_target_vpool.toString(), capabilities.getFileReplicationTargetVPool()); } } } if (capabilities.getSupportsSoftLimit()) { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.support_soft_limit.name(), capabilities.getSupportsSoftLimit()); } if (capabilities.getSupportsNotificationLimit()) { provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.support_notification_limit.name(), capabilities.getSupportsNotificationLimit()); } if (!(VirtualPool.vPoolSpecifiesProtection(vpool) || VirtualPool.vPoolSpecifiesSRDF(vpool) || VirtualPool.vPoolSpecifiesHighAvailability(vpool) || VirtualPool.vPoolSpecifiesHighAvailabilityDistributed(vpool))) { // only enforce array affinity policy for vpool without RP, SRDF, VPLEX boolean arrayAffinity = VirtualPool.ResourcePlacementPolicyType.array_affinity.name().equals(vpool.getPlacementPolicy()); if (arrayAffinity && capabilities.getCompute() != null) { capabilities.put(VirtualPoolCapabilityValuesWrapper.ARRAY_AFFINITY, true); provMapBuilder.putAttributeInMap(AttributeMatcher.Attributes.array_affinity.name(), true); } } Map<String, Object> attributeMap = provMapBuilder.buildMap(); if (optionalAttributes != null) { attributeMap.putAll(optionalAttributes); } _log.info("Populated attribute map: {}", attributeMap); StringBuffer errorMessage = new StringBuffer(); // Execute basic precondition check to verify that vArray has active storage pools in the vPool. // We will return a more accurate error condition if this basic check fails. List<StoragePool> matchedPools = _matcherFramework.matchAttributes( matchedPoolsForCos, attributeMap, _dbClient, _coordinator, AttributeMatcher.BASIC_PLACEMENT_MATCHERS, errorMessage); if (matchedPools == null || matchedPools.isEmpty()) { _log.warn("vPool {} does not have active storage pools in vArray {} .", vpool.getId(), varray.getId()); throw APIException.badRequests.noStoragePools(varray.getLabel(), vpool.getLabel(), errorMessage.toString()); } errorMessage.setLength(0); // Matches the pools against the VolumeParams. // Use a set of matched pools returned from the basic placement matching as the input for this call. matchedPools = _matcherFramework.matchAttributes( matchedPools, attributeMap, _dbClient, _coordinator, AttributeMatcher.PLACEMENT_MATCHERS, errorMessage); if (matchedPools == null || matchedPools.isEmpty()) { if (optionalAttributes != null) { optionalAttributes.put(AttributeMatcher.ERROR_MESSAGE, errorMessage); } _log.warn("Varray {} does not have storage pools which match vpool {} properties and have specified capabilities.", varray.getId(), vpool.getId()); return storagePools; } storagePools.addAll(matchedPools); return storagePools; } /** * Try to determine a list of storage pools from the passed list of storage * pools that can accommodate the passed number of resources of the passed * size accord to array affinity policy. * * @param varrayId String of VirtualArray ID for the recommendations. * @param capabilities VirtualPoolCapabilityValuesWrapper. * @param candidatePools The list of candidate storage pools. * @param inCG In a Consistency Group * * @return The list of Recommendation instances reflecting the recommended * pools. */ private List<Recommendation> performArrayAffinityPlacement(String varrayId, VirtualPoolCapabilityValuesWrapper capabilities, List<StoragePool> candidatePools, boolean inCG) { Map<URI, Double> arrayToHostWeightMap = new HashMap<URI, Double>(); Map<URI, Set<URI>> preferredPoolMap = null; boolean canUseNonPreferred = false; if (capabilities.getArrayAffinity()) { String computeIdStr = capabilities.getCompute(); preferredPoolMap = getPreferredPoolMap(computeIdStr, arrayToHostWeightMap); _log.info("ArrayAffinity - preferred arrays for {} - {}", computeIdStr, arrayToHostWeightMap); int limit = Integer.valueOf(_customConfigHandler.getComputedCustomConfigValue( CustomConfigConstants.HOST_RESOURCE_MAX_NUM_OF_ARRAYS, CustomConfigConstants.GLOBAL_KEY, null)); canUseNonPreferred = preferredPoolMap.keySet().size() < limit; } else { preferredPoolMap = new HashMap<URI, Set<URI>>(); canUseNonPreferred = true; } _log.info("ArrayAffinity - allow non preferred array {}", canUseNonPreferred); // group pools by array Map<URI, List<StoragePool>> candidatePoolMap = groupPoolsByArray(candidatePools, canUseNonPreferred, arrayToHostWeightMap.keySet()); if (candidatePoolMap == null || candidatePoolMap.isEmpty()) { throw APIException.badRequests.noCandidateStoragePoolsForArrayAffinity(); } // get all the candidate arrays List<StorageSystem> candidateSystems = _dbClient.queryObject(StorageSystem.class, candidatePoolMap.keySet()); // all pools that can be used for placement List<StoragePool> poolList = new ArrayList<StoragePool>(); for (List<StoragePool> pools : candidatePoolMap.values()) { poolList.addAll(pools); } // compute and set storage pools' and arrays' average port usage metrics before sorting _log.info("ArrayAffinity - compute port metrics"); Map<URI, Double> arrayToAvgPortMetricsMap = _portMetricsProcessor.computeStoragePoolsAvgPortMetrics(poolList); // sort the arrays, first by host/cluster's preference, then by array's average port metrics // then by free capacity and capacity utilization Collections.sort(candidateSystems, new StorageSystemArrayAffinityComparator(arrayToHostWeightMap, candidatePoolMap, arrayToAvgPortMetricsMap)); _log.info("ArrayAffinity - sorted candidate systems {}", Joiner.on(',').join(Collections2.transform(candidateSystems, CommonTransformerFunctions.fctnDataObjectToID()))); // process the sorted candidate arrays for (StorageSystem system : candidateSystems) { URI systemURI = system.getId(); // get all available pools of the array List<StoragePool> availablePools = candidatePoolMap.get(system.getId()); // sort the pools by free capacity, and capacity utilization StoragePoolCapacityComparator poolComparator = new StoragePoolCapacityComparator(); Collections.sort(availablePools, poolComparator); // split the pools into two lists by preference, both are sorted Set<URI> preferredPoolURIs = preferredPoolMap.get(systemURI); List<StoragePool> preferredPools = new ArrayList<StoragePool>(); List<StoragePool> nonPreferredPools = new ArrayList<StoragePool>(); for (StoragePool pool : availablePools) { if (preferredPoolURIs != null && preferredPoolURIs.contains(pool.getId())) { preferredPools.add(pool); } else { nonPreferredPools.add(pool); } } // create a list of secondary pools (preferred and non preferred) // the list is sorted by preference first, then by capacity // the list will be used only if preferred pools along cannot satisfy the request // // IBM XIV, all volumes in a CG must belong to same pool List<StoragePool> secondaryPools = new ArrayList<StoragePool>(); // for IBM XIV, all volumes in a CG must be in same pool if (inCG && Type.ibmxiv.name().equals(system.getSystemType())) { if (preferredPools.isEmpty()) { if (!nonPreferredPools.isEmpty()) { // only keep first non preferred pool secondaryPools.add(nonPreferredPools.get(0)); } } else { // only keep first preferred pool preferredPools = Arrays.asList(preferredPools.get(0)); // only keep the first non preferred pool if it has more capacity than the preferred one if (!nonPreferredPools.isEmpty() && poolComparator.compare(nonPreferredPools.get(0), preferredPools.get(0)) < 0) { secondaryPools.add(nonPreferredPools.get(0)); } } } else { // preferred pools alone will be tried first, then the secondary pools will be tried // only if there is a non preferred pool if (!nonPreferredPools.isEmpty()) { secondaryPools.addAll(preferredPools); secondaryPools.addAll(nonPreferredPools); } } // start from preferredPools if (!preferredPools.isEmpty()) { _log.info("ArrayAffinity - preferred pools {}", Joiner.on(',').join(preferredPools)); List<Recommendation> recommendations = getRecommendedPools(varrayId, preferredPools, capabilities, false); if (!recommendations.isEmpty()) { return recommendations; } else { _log.info("ArrayAffinity - no recommended pools found from perferred pools"); } } // then secondaryPools if (!secondaryPools.isEmpty()) { _log.info("ArrayAffinity - secondary pools {}", Joiner.on(',').join(secondaryPools)); // send both preferred and non preferred pools for the system List<Recommendation> recommendations = getRecommendedPools(varrayId, availablePools, capabilities, false); if (!recommendations.isEmpty()) { return recommendations; } else { _log.info("ArrayAffinity - no recommended pools found from secondary pools"); } } } // No recommendations found on any storage system. return new ArrayList<Recommendation>(); } /** * Find out storage systems and pools of existing mapped volumes of a host/cluster * * @param compute URI string of the host or cluster * @param arrayToHostWeightMap map of system URI to weight of hosts for which the array is a preferred array * * @return a map of system URI to URIs of pools */ private Map<URI, Set<URI>> getPreferredPoolMap(String compute, Map<URI, Double> arrayToHostWeightMap) { if (compute == null) { return new HashMap<URI, Set<URI>>(); } URI computeURI = URIUtil.uri(compute); if (URIUtil.isType(computeURI, Cluster.class)) { return getPreferredPoolMapForCluster(computeURI, arrayToHostWeightMap); } else { return getPreferredPoolMapForHost(computeURI, arrayToHostWeightMap, ExportGroup.ExportGroupType.Host.name()); } } /** * Find out storage systems and pools of existing mapped volumes of a cluster * * @param clusterURI URI of the cluster * @param arrayToHostWeightMap map of system URI to count of hosts for which the array is a preferred array * * @return a map of system URI to URIs of pools */ private Map<URI, Set<URI>> getPreferredPoolMapForCluster(URI clusterURI, Map<URI, Double> arrayToHostWeightMap) { Map<URI, Set<URI>> poolMap = new HashMap<URI, Set<URI>>(); List<Host> hosts = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, Host.class, ContainmentConstraint.Factory.getContainedObjectsConstraint(clusterURI, Host.class, "cluster")); for (Host host : hosts) { Map<URI, Set<URI>> arrayToPoolsMap = getPreferredPoolMapForHost(host.getId(), arrayToHostWeightMap, ExportGroupType.Cluster.name()); for (Map.Entry<URI, Set<URI>> entry : arrayToPoolsMap.entrySet()) { URI systemURI = entry.getKey(); Set<URI> pools = poolMap.get(systemURI); if (pools == null) { pools = new HashSet<URI>(); poolMap.put(systemURI, pools); } pools.addAll(entry.getValue()); } } return poolMap; } /** * Find out storage systems and pools of existing mapped volumes of a host * * @param hostURI URI of the host * @param arrayToHostWeightMap map of system URI to weight of hosts for which the array is a preferred array * @param exportType Cluster or Host export * @return a map of system URI to URIs of pools */ private Map<URI, Set<URI>> getPreferredPoolMapForHost(URI hostURI, Map<URI, Double> arrayToHostWeightMap, String exportType) { Map<URI, String> poolToTypeMap = new HashMap<URI, String>(); Host host = _dbClient.queryObject(Host.class, hostURI); if (host != null && !host.getInactive()) { // add preferred pool Ids from array affinity discovery _log.info("ArrayAffinity - host {} - preferredPools {}", hostURI.toString(), CommonTransformerFunctions.collectionString(host.getPreferredPools())); for (Map.Entry<String, String> entry : host.getPreferredPools().entrySet()) { poolToTypeMap.put(URI.create(entry.getKey()), entry.getValue()); } // discover preferred pools from ViPR DB List<ExportGroup> exportGroups = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, ExportGroup.class, AlternateIdConstraint.Factory.getConstraint(ExportGroup.class, "hosts", hostURI.toString())); for (ExportGroup exportGroup : exportGroups) { StringMap volumeStringMap = exportGroup.getVolumes(); String type = exportGroup.getType(); if (volumeStringMap != null && !volumeStringMap.isEmpty()) { for (String volumeIdStr : volumeStringMap.keySet()) { URI volumeURI = URI.create(volumeIdStr); if (URIUtil.isType(volumeURI, Volume.class)) { Volume volume = _dbClient.queryObject(Volume.class, volumeURI); if (volume != null && !volume.getInactive()) { URI poolURI = volume.getPool(); String oldType = poolToTypeMap.get(poolURI); if (oldType == null || (!oldType.equals(type) && type.equals(ExportGroupType.Cluster.name()))) { poolToTypeMap.put(poolURI, type); _log.info("ArrayAffinity - host {} preferred pool in ViPR - {}, type {}", hostURI.toString(), poolURI.toString(), type); } } } } } } } // group pools by array Map<URI, Set<URI>> arrayToPoolsMap = new HashMap<URI, Set<URI>>(); if (!poolToTypeMap.isEmpty()) { List<StoragePool> pools = _dbClient.queryObject(StoragePool.class, poolToTypeMap.keySet()); for (StoragePool pool : pools) { if (pool != null && !pool.getInactive()) { URI systemURI = pool.getStorageDevice(); Set<URI> groupPools = arrayToPoolsMap.get(systemURI); if (groupPools == null) { groupPools = new HashSet<URI>(); arrayToPoolsMap.put(systemURI, groupPools); } groupPools.add(pool.getId()); } } // calculate weight // if any of preferred pools is shared, the array is shared // if all of the preferred pools are exclusive, the array is exclusive for (URI systemURI : arrayToPoolsMap.keySet()) { Set<URI> groupPools = arrayToPoolsMap.get(systemURI); boolean hasCluster = false; for (URI poolURI : groupPools) { if (ExportGroupType.Cluster.name().equals(poolToTypeMap.get(poolURI))) { hasCluster = true; break; } } Double weight = 1.0; if (ExportGroupType.Host.name().equals(exportType)) { if (hasCluster) { weight = AFFINITY_FACTOR; } } else { // cluster if (!hasCluster) { weight = AFFINITY_FACTOR; } } // update weigth Double oldWeight = arrayToHostWeightMap.get(systemURI); if (oldWeight == null) { oldWeight = 0.0; } arrayToHostWeightMap.put(systemURI, oldWeight + weight); } } return arrayToPoolsMap; } /* * Group storage pools by storage array * * @param pools storage pool to be grouped * * @param canUseNonPreferred boolean if non preferred systems can be used * * @param preferredSystemIds Ids of preferred systems * * @return a map of storage system URI to URIs of storage pools */ private Map<URI, List<StoragePool>> groupPoolsByArray(List<StoragePool> pools, boolean canUseNonPreferred, Set<URI> preferredSystemIds) { Map<URI, List<StoragePool>> poolMap = new HashMap<URI, List<StoragePool>>(); for (StoragePool pool : pools) { if (pool != null && !pool.getInactive()) { URI systemURI = pool.getStorageDevice(); if (canUseNonPreferred || preferredSystemIds.contains(systemURI)) { List<StoragePool> groupPools = poolMap.get(systemURI); if (groupPools == null) { groupPools = new ArrayList<StoragePool>(); poolMap.put(systemURI, groupPools); } groupPools.add(pool); } } } return poolMap; } /** * Returns first storage pool from the passed list of candidate storage * pools that has at least the passed free capacity. * Note: do not change order of candidate pools. * * @param capacity The desired free capacity. * @param resourceSize The desired resource size * @param newResourceCount The desired number of resources * @param candidatePools The list of candidate storage pools. * @param isThinlyProvisioned Indication if this is thin provisioning (thin volume). * * @return A storage pool that have the passed free capacity. */ protected StoragePool getPoolMatchingCapacity(long capacity, long resourceSize, Integer newResourceCount, List<StoragePool> candidatePools, boolean isThinlyProvisioned, Long thinVolumePreAllocationResourceSize) { StoragePool poolWithCapacity = null; CapacityMatcher capacityMatcher = new CapacityMatcher(); capacityMatcher.setCoordinatorClient(_coordinator); capacityMatcher.setObjectCache(new ObjectLocalCache(_dbClient, false)); Iterator<StoragePool> storagePoolsIter = candidatePools.iterator(); while (storagePoolsIter.hasNext()) { StoragePool candidatePool = storagePoolsIter.next(); // First check if max Resources limit is violated for the pool if (MaxResourcesMatcher.checkPoolMaximumResourcesApproached(candidatePool, _dbClient, newResourceCount)) { continue; } // TODO Used to force placement to VNX Concrete pools. // if // (candidatePool.getPoolClassName().equalsIgnoreCase(StoragePool.PoolClassNames.Clar_UnifiedStoragePool.toString())) // continue; if (capacityMatcher.poolMatchesCapacity(candidatePool, capacity, resourceSize, false, isThinlyProvisioned, thinVolumePreAllocationResourceSize)) { poolWithCapacity = candidatePool; break; } } return poolWithCapacity; } /** * Returns all storage pools from the passed list of candidate storage * pools that have at least the passed free capacity. * Note: do not change order of candidate pools. * * @param capacity The desired free capacity. * @param resourceSize The desired resource size * @param newResourceCount The desired number of resources * @param candidatePools The list of candidate storage pools. * @param isThinlyProvisioned Indication if this is thin provisioning (thin volume). * * @return All storage pools that have the passed free capacity. */ protected List<StoragePool> getPoolsMatchingCapacity(long capacity, long resourceSize, Integer newResourceCount, List<StoragePool> candidatePools, boolean isThinlyProvisioned, Long thinVolumePreAllocationResourceSize) { List<StoragePool> poolsWithCapacity = new ArrayList<StoragePool>(); CapacityMatcher capacityMatcher = new CapacityMatcher(); capacityMatcher.setCoordinatorClient(_coordinator); capacityMatcher.setObjectCache(new ObjectLocalCache(_dbClient, false)); for (StoragePool candidatePool : candidatePools) { // First check if max Resources limit is violated for the pool if (MaxResourcesMatcher.checkPoolMaximumResourcesApproached(candidatePool, _dbClient, newResourceCount)) { continue; } // TODO Used to force placement to VNX Concrete pools. // if // (candidatePool.getPoolClassName().equalsIgnoreCase(StoragePool.PoolClassNames.Clar_UnifiedStoragePool.toString())) // continue; if (capacityMatcher.poolMatchesCapacity(candidatePool, capacity, resourceSize, false, isThinlyProvisioned, thinVolumePreAllocationResourceSize)) { poolsWithCapacity.add(candidatePool); } } return poolsWithCapacity; } /** * Select one storage pool out a list of candidates. Use static and dynamic loads, capacity etc * criteria to narrow the selection. * * @param poolList - List of StoragePools that meet the placement criteria for the * volume. * @return - A StoragePool that can be used to allocate the volume, * which meets the VirtualPool and capacity requirements. */ public StoragePool selectPool(List<StoragePool> poolList) { if (poolList == null || poolList.isEmpty()) { return null; } // compute and set storage pools' average port usage metrics before sorting. _portMetricsProcessor.computeStoragePoolsAvgPortMetrics(poolList); // Select from the poolList the one that has the largest free capacity with least usage port metric Collections.sort(poolList, _storagePoolComparator); return poolList.get(0); } /** * Try to determine a list of storage pools from the passed list of storage * pools that can accommodate the passed number of resources of the passed * size. * * @param varrayId The VirtualArray for the recommendations. * @param candidatePools The list of candidate storage pools. * * @return The list of Recommendation instances reflecting the recommended * pools. */ protected List<Recommendation> getRecommendationsForPools(String varrayId, List<StoragePool> candidatePools, VirtualPoolCapabilityValuesWrapper capabilities) { // If the capabilities specify a CG and the CG has yet to actually be // created on a physical device, we need to make sure that the call // returns recommended storage pools that are all on the same storage // system. If the CG is created, the passed list of candidate pools // will already filtered to that storage system. However, when it is // not created, the candidate pools are all pools on all systems that // / satisfy the placement criteria. Also, if only one volume is being // provisioned, it becomes irrelevant as only one storage pool will // be recommended. URI cgURI = capabilities.getBlockConsistencyGroup(); int count = capabilities.getResourceCount(); boolean inCG = false; if ((count > 1) && (!NullColumnValueGetter.isNullURI(cgURI))) { BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgURI); if (cg == null) { throw APIException.internalServerErrors.invalidObject(cgURI.toString()); } if (!cg.created()) { // don't know ConsistencyGroup's StorageSystem type yet inCG = true; } } // Handle array affinity placement // If inCG is true, it is similar to the array affinity case. // Only difference is that resources will not be placed to more than one preferred systems if inCG is true if (capabilities.getResourceCount() > 1 && inCG || capabilities.getArrayAffinity()) { _log.info("Calling performArrayAffinityPlacement"); List<Recommendation> recommendations = performArrayAffinityPlacement(varrayId, capabilities, candidatePools, inCG); if (!recommendations.isEmpty()) { return recommendations; } else { // No recommendations found on any storage system. return new ArrayList<Recommendation>(); } } // this is the behavior without array affinity policy _log.info("Calling getRecommendedPools"); return getRecommendedPools(varrayId, candidatePools, capabilities, true); } /** * Try to determine a list of storage pools from the passed list of storage * pools that can accommodate the passed number of resources of the passed * size. * * @param varrayId The VirtualArray for the recommendations. * @param candidatePools The list of candidate storage pools. * @param capabilities The characteristics of the recommendation request. * @param orderPools true if candidate pools should be ordered before * determining the recommendation, false otherwise * * @return The list of Recommendation instances reflecting the recommended * pools. */ protected List<Recommendation> getRecommendedPools(String varrayId, List<StoragePool> candidatePools, VirtualPoolCapabilityValuesWrapper capabilities, boolean orderPools) { List<Recommendation> recommendations = new ArrayList<Recommendation>(); long thinVolumePreAllocateSize = capabilities.getThinVolumePreAllocateSize(); if (orderPools) { // Sort all pools in descending order by free capacity (first order) // and in ascending order by ratio of pool's subscribed capacity to // total capacity(suborder). This order is kept through the // selection procedure. sortPools(candidatePools); } // We need to create recommendations for one or more pools // that can accommodate the number of requested resources. // We start by trying to place all resources in a single // pool if one exists that can accommodate all requested // resources and work our way down as necessary trying to // minimize the number of pools used to satisfy the request. int recommendedCount = 0; int currentCount = capabilities.getResourceCount(); while ((!candidatePools.isEmpty()) && (recommendedCount < capabilities.getResourceCount()) && (currentCount > 0)) { long requiredPoolCapacity = capabilities.getSize() * currentCount; long reqThinVolumePreAllocateSize = thinVolumePreAllocateSize * currentCount; StoragePool poolWithRequiredCapacity = getPoolMatchingCapacity(requiredPoolCapacity, capabilities.getSize(), currentCount, candidatePools, capabilities.getThinProvisioning(), reqThinVolumePreAllocateSize); if (poolWithRequiredCapacity != null) { StoragePool recommendedPool = poolWithRequiredCapacity; candidatePools.remove(recommendedPool); _log.debug("Recommending storage pool {} for {} resources.", recommendedPool.getId(), currentCount); Recommendation recommendation = new Recommendation(); recommendation.setSourceStoragePool(recommendedPool.getId()); recommendation.setResourceCount(currentCount); recommendation.setSourceStorageSystem(recommendedPool.getStorageDevice()); recommendations.add(recommendation); // Update the count of resources for which we have created // a recommendation. recommendedCount += currentCount; // Update the current count. The conditional prevents // unnecessary attempts to look for pools of a given // free capacity that we already know don't exist. For // example, say we want 100 resources and the first pool // we find that can hold multiple resources can hold only // 10. We don't want to continue looking for pools that // can hold 90,89,88,...11 resources. We just want to // see if there is another pool that can hold 10 resources, // then 9,8, and so on. currentCount = (capabilities.getResourceCount() - recommendedCount < currentCount ? capabilities.getResourceCount() - recommendedCount : currentCount); } else { // If we can't find a pool that can hold the current // count of resources, decrease the count so that we look // for pools that can hold the next smaller number. currentCount--; } } // We need to place all the resources. If we can't then // log an error and clear the list of recommendations. if (recommendedCount != capabilities.getResourceCount()) { recommendations.clear(); } return recommendations; } /** * Sort all pools in ascending order of its storage system's average port usage metrics (first order), * descending order by free capacity (second order) and in ascending order by ratio * of pool's subscribed capacity to total capacity(suborder). */ public static class StoragePoolDefaultComparator implements Comparator<StoragePool> { @Override public int compare(StoragePool sp1, StoragePool sp2) { int result = 0; // if avg port metrics was not computable, consider its usage is max out for sorting purpose double sp1AvgPortMetrics = sp1.getAvgStorageDevicePortMetrics() == null || sp1.getAvgStorageDevicePortMetrics() < 0.0 ? Double.MAX_VALUE : sp1.getAvgStorageDevicePortMetrics(); double sp2AvgPortMetrics = sp2.getAvgStorageDevicePortMetrics() == null || sp2.getAvgStorageDevicePortMetrics() < 0.0 ? Double.MAX_VALUE : sp2.getAvgStorageDevicePortMetrics(); if (sp1.getFreeCapacity() > 0 && sp2.getFreeCapacity() > 0) { // compare metrics if they have free capacity result = Double.compare(sp1AvgPortMetrics, sp2AvgPortMetrics); } if (result == 0) { result = Long.compare(sp2.getFreeCapacity(), sp1.getFreeCapacity()); // descending order } if (result == 0) { result = Double.compare(sp1.getSubscribedCapacity().doubleValue() / sp1.getTotalCapacity(), sp2.getSubscribedCapacity().doubleValue() / sp2.getTotalCapacity()); } // if result is 1, swap return result; } } /** * Sort pools in descending order by free capacity (first order) and in ascending order by ratio * of pool's subscribed capacity to total capacity(second order). */ private class StoragePoolCapacityComparator implements Comparator<StoragePool> { @Override public int compare(StoragePool sp1, StoragePool sp2) { int result = Long.compare(sp2.getFreeCapacity(), sp1.getFreeCapacity()); // descending order if (result == 0) { result = Double.compare(sp1.getSubscribedCapacity().doubleValue() / sp1.getTotalCapacity(), sp2.getSubscribedCapacity().doubleValue() / sp2.getTotalCapacity()); } return result; } } /** * Sort storage systems in descending order of its hosts count (first order), * and in ascending order of its average port usage metrics (second order), * and in descending order by system's candidate pools' overall free capacity (third order), * and in ascending order by ratio of system's candidate pools' overall subscribed capacity to overall total capacity (fourth order) */ private class StorageSystemArrayAffinityComparator implements Comparator<StorageSystem> { private Map<URI, Double> arrayToHostWeight; private Map<URI, List<StoragePool>> candidatePoolMap; private Map<URI, Double> arrayToAvgPortMetricsMap; public StorageSystemArrayAffinityComparator(Map<URI, Double> arrayToHostWeight, Map<URI, List<StoragePool>> candidatePoolMap, Map<URI, Double> arrayToAvgPortMetricsMap) { this.arrayToHostWeight = arrayToHostWeight; this.candidatePoolMap = candidatePoolMap; this.arrayToAvgPortMetricsMap = arrayToAvgPortMetricsMap; } @Override public int compare(StorageSystem sys1, StorageSystem sys2) { int result = 0; if (arrayToHostWeight != null && !arrayToHostWeight.isEmpty()) { Double sys1HostWeight = arrayToHostWeight.get(sys1.getId()) == null ? 0.0 : arrayToHostWeight.get(sys1.getId()); Double sys2HostWeight = arrayToHostWeight.get(sys2.getId()) == null ? 0.0 : arrayToHostWeight.get(sys2.getId()); result = Double.compare(sys2HostWeight, sys1HostWeight); } if (result == 0) { Double sys1Metric = arrayToAvgPortMetricsMap.get(sys1.getId()); Double sys2Metric = arrayToAvgPortMetricsMap.get(sys2.getId()); result = Double.compare(sys1Metric, sys2Metric); } if (result == 0) { Long sys1FreeCapacity = getFreeCapacityForSystemPools(candidatePoolMap.get(sys1.getId())); Long sys2FreeCapacity = getFreeCapacityForSystemPools(candidatePoolMap.get(sys2.getId())); result = Long.compare(sys2FreeCapacity, sys1FreeCapacity); } if (result == 0) { Long sys1SubscribedCapacity = getSubscribedCapacityForSystemPools(candidatePoolMap.get(sys1.getId())); Long sys2SubscribedCapacity = getSubscribedCapacityForSystemPools(candidatePoolMap.get(sys2.getId())); Long sys1TotalCapacity = getTotalCapacityForSystemPools(candidatePoolMap.get(sys1.getId())); Long sys2TotalCapacity = getTotalCapacityForSystemPools(candidatePoolMap.get(sys2.getId())); result = Double.compare(sys1SubscribedCapacity.doubleValue() / sys1TotalCapacity, sys2SubscribedCapacity.doubleValue() / sys2TotalCapacity); } return result; } /** * Gets the overall free capacity for the given candidate pools of a system. */ private long getFreeCapacityForSystemPools(List<StoragePool> pools) { long freeCapacity = 0; for (StoragePool pool : pools) { freeCapacity += pool.getFreeCapacity(); } return freeCapacity; } /** * Gets the overall subscribed capacity for the given candidate pools of a system. */ private Long getSubscribedCapacityForSystemPools(List<StoragePool> pools) { long subscribedCapacity = 0; for (StoragePool pool : pools) { subscribedCapacity += pool.getSubscribedCapacity(); } return subscribedCapacity; } /** * Gets the overall total capacity for the given candidate pools of a system. */ private long getTotalCapacityForSystemPools(List<StoragePool> pools) { long totalCapacity = 0; for (StoragePool pool : pools) { totalCapacity += pool.getTotalCapacity(); } return totalCapacity; } } /** * Create volumes from recommendation object. * * @param param volume creation parameters * @param task task * @param taskList task list * @param project project * @param neighborhood virtual array * @param vPool virtual pool * @param volumeCount number of volumes to create * @param recommendations recommendation structures * @param consistencyGroup consistency group to use * @param volumeCounter how many volumes are created * @param volumeLabel volume label * @param preparedVolumes volumes that have been prepared * @param cosCapabilities virtual pool wrapper * @param createInactive create the device in an inactive state */ /** * Create volumes from recommendations objects. * * @param size -- size of volumes in bytes * @param task -- overall task id * @param taskList -- a TaskList new tasks may be inserted into * @param project -- Project object * @param neighborhood -- Virtual array * @param vPool -- Virtual pool * @param volumeCount -- number of like volumes to be created * @param recommendations -- List of Recommendation objects describing pools to use for volumes * @param consistencyGroup -- The BlockConsistencyGroup object to be used for the volumes * @param volumeCounter -- The current volume counter, used to generate unique names for like volumes * @param volumeLabel -- Label (prefix) of the volumes to be created * @param preparedVolumes -- Output argument that receives the prepared volumes * @param cosCapabilities - VirtualPoolCapabilityValuesWrapper contains parameters for volume creation * @param createInactive-- used to set the Volume syncActive flag (to the inverted sense of createInactive) */ public void prepareRecommendedVolumes(Long size, String task, TaskList taskList, Project project, VirtualArray neighborhood, VirtualPool vPool, Integer volumeCount, List<Recommendation> recommendations, BlockConsistencyGroup consistencyGroup, int volumeCounter, String volumeLabel, List<Volume> preparedVolumes, VirtualPoolCapabilityValuesWrapper cosCapabilities, Boolean createInactive) { Iterator<Recommendation> recommendationsIter = recommendations.iterator(); while (recommendationsIter.hasNext()) { VolumeRecommendation recommendation = (VolumeRecommendation) recommendationsIter.next(); // if id is already set in recommendation, do not prepare the volume (volume already exists) if (recommendation.getId() != null) { continue; } // prepare block volume if (recommendation.getType().toString().equals(VolumeRecommendation.VolumeType.BLOCK_VOLUME.toString())) { String newVolumeLabel = AbstractBlockServiceApiImpl.generateDefaultVolumeLabel(volumeLabel, volumeCounter++, volumeCount); // Grab the existing volume and task object from the incoming task list Volume volume = getPrecreatedVolume(_dbClient, taskList, newVolumeLabel); boolean volumePrecreated = false; if (volume != null) { volumePrecreated = true; } long thinVolumePreAllocationSize = 0; if (null != vPool.getThinVolumePreAllocationPercentage()) { thinVolumePreAllocationSize = VirtualPoolUtil.getThinVolumePreAllocationSize( vPool.getThinVolumePreAllocationPercentage(), size); } volume = prepareVolume(_dbClient, volume, size, thinVolumePreAllocationSize, project, neighborhood, vPool, recommendation, newVolumeLabel, consistencyGroup, cosCapabilities, createInactive); // set volume id in recommendation recommendation.setId(volume.getId()); // add volume to reserved capacity map of storage pool addVolumeCapacityToReservedCapacityMap(_dbClient, volume); preparedVolumes.add(volume); if (!volumePrecreated) { Operation op = _dbClient.createTaskOpStatus(Volume.class, volume.getId(), task, ResourceOperationTypeEnum.CREATE_BLOCK_VOLUME); volume.getOpStatus().put(task, op); TaskResourceRep volumeTask = toTask(volume, task, op); // This task addition is inconsequential since we've already returned the source volume tasks. // It is good to continue to have a task associated with this volume AND store its status in the volume. taskList.getTaskList().add(volumeTask); } } else if (recommendation.getType().toString().equals(VolumeRecommendation.VolumeType.BLOCK_LOCAL_MIRROR.toString())) { // prepare local mirror based on source volume and storage pool recommendation VolumeRecommendation volumeRecommendation = (VolumeRecommendation) recommendation .getParameter(VolumeRecommendation.BLOCK_VOLUME); URI volumeId = volumeRecommendation.getId(); Volume volume = _dbClient.queryObject(Volume.class, volumeId); String mirrorLabel = volumeLabel; if (volume.isInCG()) { mirrorLabel = ControllerUtils.getMirrorLabel(volume.getLabel(), volumeLabel); } if (volumeCount > 1) { mirrorLabel = ControllerUtils.getMirrorLabel(mirrorLabel, volumeCounter++); } // Prepare a single mirror based on source volume and storage pool recommendation BlockMirror mirror = initializeMirror(volume, vPool, recommendation.getCandidatePools().get(0), mirrorLabel, _dbClient); // set mirror id in recommendation recommendation.setId(mirror.getId()); preparedVolumes.add(mirror); // add mirror to reserved capacity map of storage pool addVolumeCapacityToReservedCapacityMap(_dbClient, mirror); } } } /** * Convenience method to return a volume from a task list with a pre-labeled volume number. * * @param dbClient dbclient * @param taskList task list * @param label base label * @return Volume object */ public static Volume getPrecreatedVolume(DbClient dbClient, TaskList taskList, String label) { // The label we've been given has already been appended with the appropriate volume number String volumeLabel = AbstractBlockServiceApiImpl.generateDefaultVolumeLabel(label, 0, 1); if (taskList == null) { return null; } for (TaskResourceRep task : taskList.getTaskList()) { Volume volume = dbClient.queryObject(Volume.class, task.getResource().getId()); if (volume.getLabel().equalsIgnoreCase(volumeLabel)) { return volume; } } return null; } public static Volume prepareFullCopyVolume(DbClient dbClient, String name, BlockObject sourceVolume, VolumeRecommendation recommendation, int volumeCounter, VirtualPoolCapabilityValuesWrapper capabilities) { return prepareFullCopyVolume(dbClient, name, sourceVolume, recommendation, volumeCounter, capabilities, false); } public static Volume prepareFullCopyVolume(DbClient dbClient, String name, BlockObject sourceVolume, VolumeRecommendation recommendation, int volumeCounter, VirtualPoolCapabilityValuesWrapper capabilities, Boolean createInactive) { if (sourceVolume instanceof BlockSnapshot) { return prepareFullCopyVolumeFromSnapshot(dbClient, name, ((BlockSnapshot) sourceVolume), recommendation, volumeCounter, capabilities, createInactive); } else { return prepareFullCopyVolumeFromVolume(dbClient, name, ((Volume) sourceVolume), recommendation, volumeCounter, capabilities, createInactive); } } private static Volume prepareFullCopyVolumeFromVolume(DbClient dbClient, String name, Volume sourceVolume, VolumeRecommendation recommendation, int volumeCounter, VirtualPoolCapabilityValuesWrapper capabilities, Boolean createInactive) { long size = sourceVolume.getCapacity(); long preAllocateSize = 0; if (null != sourceVolume.getThinVolumePreAllocationSize()) { preAllocateSize = sourceVolume.getThinVolumePreAllocationSize(); } NamedURI projectUri = sourceVolume.getProject(); Project project = dbClient.queryObject(Project.class, projectUri); URI vArrayUri = sourceVolume.getVirtualArray(); VirtualArray vArray = dbClient.queryObject(VirtualArray.class, vArrayUri); URI vPoolUri = sourceVolume.getVirtualPool(); VirtualPool vPool = dbClient.queryObject(VirtualPool.class, vPoolUri); String label = name + (volumeCounter > 0 ? ("-" + volumeCounter) : ""); Volume volume = prepareVolume(dbClient, null, size, preAllocateSize, project, vArray, vPool, recommendation, label, null, capabilities, createInactive); // Since this is a full copy, update it with URI of the source volume volume.setAssociatedSourceVolume(sourceVolume.getId()); StringSet associatedFullCopies = sourceVolume.getFullCopies(); if (associatedFullCopies == null) { associatedFullCopies = new StringSet(); sourceVolume.setFullCopies(associatedFullCopies); } associatedFullCopies.add(volume.getId().toString()); dbClient.persistObject(volume); dbClient.persistObject(sourceVolume); addVolumeCapacityToReservedCapacityMap(dbClient, volume); return volume; } private static Volume prepareFullCopyVolumeFromSnapshot(DbClient dbClient, String name, BlockSnapshot sourceSnapshot, VolumeRecommendation recommendation, int volumeCounter, VirtualPoolCapabilityValuesWrapper capabilities, Boolean createInactive) { // Get the parent of the snapshot, to know vpool NamedURI parentVolUri = sourceSnapshot.getParent(); Volume parentVolume = dbClient.queryObject(Volume.class, parentVolUri); URI vPoolUri = parentVolume.getVirtualPool(); URI projectUri = sourceSnapshot.getProject().getURI(); long size = sourceSnapshot.getProvisionedCapacity(); long preAllocateSize = sourceSnapshot.getAllocatedCapacity(); Project project = dbClient.queryObject(Project.class, projectUri); URI vArrayUri = sourceSnapshot.getVirtualArray(); VirtualArray vArray = dbClient.queryObject(VirtualArray.class, vArrayUri); VirtualPool vPool = dbClient.queryObject(VirtualPool.class, vPoolUri); String label = name + (volumeCounter > 0 ? ("-" + volumeCounter) : ""); Volume volume = prepareVolume(dbClient, null, size, preAllocateSize, project, vArray, vPool, recommendation, label, null, capabilities, createInactive); // Since this is a full copy, update it with URI of the source snapshot volume.setAssociatedSourceVolume(sourceSnapshot.getId()); dbClient.persistObject(volume); dbClient.persistObject(sourceSnapshot); addVolumeCapacityToReservedCapacityMap(dbClient, volume); return volume; } public static Volume prepareVolume(DbClient dbClient, Volume volume, long size, long thinVolumePreAllocationSize, Project project, VirtualArray neighborhood, VirtualPool vpool, VolumeRecommendation placement, String label, BlockConsistencyGroup consistencyGroup, VirtualPoolCapabilityValuesWrapper capabilities) { return prepareVolume(dbClient, volume, size, thinVolumePreAllocationSize, project, neighborhood, vpool, placement, label, consistencyGroup, capabilities, false); } /** * Prepare a new volume object in the database that can be tracked and overridden as the volume goes through the * placement process. * * @param dbClient dbclient * @param size size of volume * @param project project * @param varray virtual array * @param vpool virtual pool * @param label base volume label * @param volNumber a temporary label for this volume to mark which one it is * @param volumesRequested how many volumes were requested overall * @return a Volume object */ public static Volume prepareEmptyVolume(DbClient dbClient, long size, Project project, VirtualArray varray, VirtualPool vpool, String label, int volNumber, int volumesRequested) { Volume volume = new Volume(); volume.setId(URIUtil.createId(Volume.class)); String volumeLabel = AbstractBlockServiceApiImpl.generateDefaultVolumeLabel(label, volNumber, volumesRequested); List<Volume> volumeList = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, Volume.class, ContainmentPrefixConstraint.Factory.getFullMatchConstraint(Volume.class, "project", project.getId(), volumeLabel)); if (!volumeList.isEmpty()) { throw APIException.badRequests.duplicateLabel(volumeLabel); } volume.setLabel(volumeLabel); volume.setCapacity(size); volume.setThinlyProvisioned(VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase(vpool.getSupportedProvisioningType())); volume.setVirtualPool(vpool.getId()); volume.setProject(new NamedURI(project.getId(), volume.getLabel())); volume.setTenant(new NamedURI(project.getTenantOrg().getURI(), volume.getLabel())); volume.setVirtualArray(varray.getId()); volume.setOpStatus(new OpStatusMap()); if (vpool.getDedupCapable() != null) { volume.setIsDeduplicated(vpool.getDedupCapable()); } dbClient.createObject(volume); return volume; } /** * Prepare Volume for an unprotected traditional block volume. * * @param volume pre-created volume (optional) * @param size volume size * @param project project requested * @param neighborhood varray requested * @param vpool vpool requested * @param placement recommendation for placement * @param label volume label * @param consistencyGroup cg ID * @param createInactive * * * @return a persisted volume */ public static Volume prepareVolume(DbClient dbClient, Volume volume, long size, long thinVolumePreAllocationSize, Project project, VirtualArray neighborhood, VirtualPool vpool, VolumeRecommendation placement, String label, BlockConsistencyGroup consistencyGroup, VirtualPoolCapabilityValuesWrapper cosCapabilities, Boolean createInactive) { // In the case of a new volume that wasn't pre-created, make sure that volume doesn't already exist if (volume == null) { List<Volume> volumeList = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, Volume.class, ContainmentPrefixConstraint.Factory.getFullMatchConstraint(Volume.class, "project", project.getId(), label)); if (!volumeList.isEmpty()) { throw APIException.badRequests.duplicateLabel(label); } } boolean newVolume = false; StoragePool pool = null; if (volume == null) { newVolume = true; volume = new Volume(); volume.setId(URIUtil.createId(Volume.class)); volume.setOpStatus(new OpStatusMap()); } else { // Reload volume object from DB volume = dbClient.queryObject(Volume.class, volume.getId()); } volume.setSyncActive(!Boolean.valueOf(createInactive)); volume.setLabel(label); volume.setCapacity(size); if (0 != thinVolumePreAllocationSize) { volume.setThinVolumePreAllocationSize(thinVolumePreAllocationSize); } volume.setThinlyProvisioned(VirtualPool.ProvisioningType.Thin.toString().equalsIgnoreCase(vpool.getSupportedProvisioningType())); volume.setVirtualPool(vpool.getId()); volume.setProject(new NamedURI(project.getId(), volume.getLabel())); volume.setTenant(new NamedURI(project.getTenantOrg().getURI(), volume.getLabel())); volume.setVirtualArray(neighborhood.getId()); URI poolId = placement.getCandidatePools().get(0); if (null != poolId) { pool = dbClient.queryObject(StoragePool.class, poolId); if (null != pool) { volume.setProtocol(new StringSet()); volume.getProtocol().addAll( VirtualPoolUtil.getMatchingProtocols(vpool.getProtocols(), pool.getProtocols())); } } URI storageControllerUri = placement.getCandidateSystems().get(0); StorageSystem storageSystem = dbClient.queryObject(StorageSystem.class, storageControllerUri); String systemType = storageSystem.checkIfVmax3() ? DiscoveredDataObject.Type.vmax3.name() : storageSystem.getSystemType(); volume.setSystemType(systemType); volume.setStorageController(storageControllerUri); volume.setPool(poolId); if (consistencyGroup != null) { volume.setConsistencyGroup(consistencyGroup.getId()); if (!consistencyGroup.isProtectedCG()) { String rgName = consistencyGroup.getCgNameOnStorageSystem(volume.getStorageController()); if (rgName == null) { rgName = consistencyGroup.getLabel(); // for new CG } else { // if other volumes in the same CG are in an application, add this volume to the same application VolumeGroup volumeGroup = ControllerUtils.getApplicationForCG(dbClient, consistencyGroup, rgName); if (volumeGroup != null) { volume.getVolumeGroupIds().add(volumeGroup.getId().toString()); } } volume.setReplicationGroupInstance(rgName); } } if (null != cosCapabilities.getAutoTierPolicyName()) { URI autoTierPolicyUri = getAutoTierPolicy(poolId, cosCapabilities.getAutoTierPolicyName(), dbClient); if (null != autoTierPolicyUri) { volume.setAutoTieringPolicyUri(autoTierPolicyUri); } } if (vpool.getDedupCapable() != null) { volume.setIsDeduplicated(vpool.getDedupCapable()); } if (newVolume) { dbClient.createObject(volume); } else { dbClient.updateAndReindexObject(volume); } return volume; } /** * Get the AutoTierPolicy URI for a given StoragePool and auto tier policy name. * * @param pool * -- Storage Pool URI * @param policyName * -- Policy name * @param dbClient * @return URI of AutoTierPolicy, null if not found */ public static URI getAutoTierPolicy(URI pool, String policyName, DbClient dbClient) { if (pool == null || policyName == null || dbClient == null) { return null; } URIQueryResultList result = new URIQueryResultList(); // check if pool fast policy name is not dbClient.queryByConstraint( AlternateIdConstraint.Factory.getFASTPolicyByNameConstraint(policyName), result); Iterator<URI> iterator = result.iterator(); while (iterator.hasNext()) { AutoTieringPolicy policy = dbClient.queryObject(AutoTieringPolicy.class, iterator.next()); if (null == policy.getStorageSystem()) { continue; } // pool's storage system // Note that the pool can be null when the function is called while // preparing a VPLEX volume, which does not have a storage pool. StoragePool poolObj = dbClient.queryObject(StoragePool.class, pool); if ((poolObj != null) && (policy.getStorageSystem().toString().equalsIgnoreCase(poolObj.getStorageDevice().toString()))) { return policy.getId(); } } return null; } /** * Adds a BlockMirror structure for a Volume. It also calls addMirrorToVolume to * link the mirror into the volume's mirror set. * * @param volume Volume * @param vPool * @param recommendedPoolURI Pool that should be used to create the mirror * @param volumeLabel * @param dbClient * @return BlockMirror (persisted) */ public static BlockMirror initializeMirror(Volume volume, VirtualPool vPool, URI recommendedPoolURI, String volumeLabel, DbClient dbClient) { BlockMirror createdMirror = new BlockMirror(); createdMirror.setSource(new NamedURI(volume.getId(), volume.getLabel())); createdMirror.setId(URIUtil.createId(BlockMirror.class)); URI cgUri = volume.getConsistencyGroup(); if (!NullColumnValueGetter.isNullURI(cgUri)) { createdMirror.setConsistencyGroup(cgUri); } createdMirror.setLabel(volumeLabel); createdMirror.setStorageController(volume.getStorageController()); createdMirror.setSystemType(volume.getSystemType()); createdMirror.setVirtualArray(volume.getVirtualArray()); // Setting the source Volume autoTieringPolicy in Mirror. // @TODO we must accept the policy as an input for mirrors and requires API changes. // Hence for timebeing, we are setting the source policy in mirror. if (!NullColumnValueGetter.isNullURI(volume.getAutoTieringPolicyUri())) { createdMirror.setAutoTieringPolicyUri(volume.getAutoTieringPolicyUri()); } createdMirror.setProtocol(new StringSet()); createdMirror.getProtocol().addAll(volume.getProtocol()); createdMirror.setCapacity(volume.getCapacity()); createdMirror.setProject(new NamedURI(volume.getProject().getURI(), createdMirror.getLabel())); createdMirror.setTenant(new NamedURI(volume.getTenant().getURI(), createdMirror.getLabel())); createdMirror.setPool(recommendedPoolURI); createdMirror.setVirtualPool(vPool.getId()); createdMirror.setSyncState(SynchronizationState.UNKNOWN.toString()); createdMirror.setSyncType(BlockMirror.MIRROR_SYNC_TYPE); createdMirror.setThinlyProvisioned(volume.getThinlyProvisioned()); dbClient.createObject(createdMirror); addMirrorToVolume(volume, createdMirror, dbClient); return createdMirror; } public static BlockMirror initializeMirror(Volume volume, VirtualPool vPool, URI recommendedPoolURI, DbClient dbClient) { return initializeMirror(volume, vPool, recommendedPoolURI, volume.getLabel(), dbClient); } /** * Adds a Mirror structure to a Volume's mirror set. * * @param volume * @param mirror */ private static void addMirrorToVolume(Volume volume, BlockMirror mirror, DbClient dbClient) { StringSet mirrors = volume.getMirrors(); if (mirrors == null) { mirrors = new StringSet(); } mirrors.add(mirror.getId().toString()); volume.setMirrors(mirrors); // Persist changes dbClient.persistObject(volume); } public static void addVolumeCapacityToReservedCapacityMap(DbClient _dbClient, Volume volume) { Long reservedCapacity = 0L; // For thin volumes reserve only capacity required for pre-allocation (when set) if (volume.getThinlyProvisioned() && volume.getThinVolumePreAllocationSize() != null) { reservedCapacity = volume.getThinVolumePreAllocationSize(); } else if (!volume.getThinlyProvisioned()) { reservedCapacity = volume.getCapacity(); } URI poolId = volume.getPool(); StoragePool pool = _dbClient.queryObject(StoragePool.class, poolId); StringMap reservationMap = pool.getReservedCapacityMap(); reservationMap.put(volume.getId().toString(), String.valueOf(reservedCapacity)); _dbClient.persistObject(pool); } public static void addVolumeExpansionSizeToReservedCapacityMap(DbClient _dbClient, Volume volume, long expandCapacity) { Long reservedCapacity = 0L; // Add reservation for capacity to storage pool's reservation map. // Need to account for expand capacity only for thick provisioning. Expand of thin volumes do not account // for free capacity. if (!volume.getThinlyProvisioned() && expandCapacity > 0) { reservedCapacity = expandCapacity; } URI poolId = volume.getPool(); StoragePool pool = _dbClient.queryObject(StoragePool.class, poolId); StringMap reservationMap = pool.getReservedCapacityMap(); reservationMap.put(volume.getId().toString(), String.valueOf(reservedCapacity)); _dbClient.persistObject(pool); } @Override public List<Recommendation> getRecommendationsForVpool(VirtualArray vArray, Project project, VirtualPool vPool, VpoolUse vPoolUse, VirtualPoolCapabilityValuesWrapper capabilities, Map<VpoolUse, List<Recommendation>> currentRecommendations) { // Initially we're only going to return one recommendation set. List<Recommendation> recommendations = getRecommendationsForResources(vArray, project, vPool, capabilities); return recommendations; } @Override public String getSchedulerName() { return SCHEDULER_NAME; } @Override public boolean handlesVpool(VirtualPool vPool, VpoolUse vPoolUse) { // This is a bottom level scheduler, it handles everything. return true; } }