/* * Copyright (c) 2008-2011 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.plugins.discovery.smis.processor; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.StoragePool; import com.emc.storageos.db.client.model.StoragePool.PoolServiceType; import com.emc.storageos.db.client.model.StoragePool.SupportedDriveTypeValues; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StorageSystem.SupportedProvisioningTypes; import com.emc.storageos.plugins.AccessProfile; import com.emc.storageos.plugins.BaseCollectionException; import com.emc.storageos.plugins.common.Constants; import com.emc.storageos.plugins.common.domainmodel.Operation; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator; import com.emc.storageos.volumecontroller.impl.StoragePoolAssociationHelper; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableBourneEvent; import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager; import com.emc.storageos.volumecontroller.impl.monitoring.cim.enums.RecordType; import com.emc.storageos.volumecontroller.impl.plugins.SMICommunicationInterface; import com.emc.storageos.volumecontroller.impl.smis.CIMPropertyFactory; import com.emc.storageos.volumecontroller.impl.smis.SmisConstants; import com.emc.storageos.volumecontroller.impl.smis.SmisUtils; import com.emc.storageos.volumecontroller.impl.utils.DiscoveryUtils; import com.emc.storageos.volumecontroller.impl.utils.ImplicitPoolMatcher; import com.emc.storageos.volumecontroller.impl.utils.attrmatchers.CapacityMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.cim.CIMInstance; import javax.cim.CIMObjectPath; import javax.cim.UnsignedInteger16; import javax.wbem.CloseableIterator; import javax.wbem.WBEMException; import javax.wbem.client.WBEMClient; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Processor responsible for handling Provider response data and creates StoragePools. */ public class StoragePoolProcessor extends PoolProcessor { private static Logger _logger = LoggerFactory.getLogger(StoragePoolProcessor.class); private static final String OPERATIONAL_STATUS = "OperationalStatus"; private static final String DEVICE_STORAGE_POOL = "DeviceStoragePool"; private static final String EMC_DRIVE_TYPE = "EMCDiskDriveType"; private static final String MIXED_DRIVE_TYPE = "Mixed"; private static final String SPACE_STR_DELIM = " "; private static final String POOL_ID = "PoolID"; private static final String TWO = "2"; private static final String VP_COMPRESSION_STATE = "EMCVPCompressionState"; private DbClient _dbClient; private WBEMClient _cimClient; private CoordinatorClient _coordinator; private RecordableEventManager _eventManager; private AccessProfile profile = null; private List<StoragePool> _newPoolList = null; private List<StoragePool> _updatePoolList = null; /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public void processResult( Operation operation, Object resultObj, Map<String, Object> keyMap) throws BaseCollectionException { final Iterator<CIMInstance> it = (Iterator<CIMInstance>) resultObj; profile = (AccessProfile) keyMap.get(Constants.ACCESSPROFILE); try { _newPoolList = new ArrayList<StoragePool>(); _updatePoolList = new ArrayList<StoragePool>(); _dbClient = (DbClient) keyMap.get(Constants.dbClient); _cimClient = SMICommunicationInterface.getCIMClient(keyMap); _coordinator = (CoordinatorClient) keyMap.get(Constants.COORDINATOR_CLIENT); _eventManager = (RecordableEventManager) keyMap.get(Constants.EVENT_MANAGER); _logger.info("StoragePoolProcessor --- event manager: " + _eventManager); StorageSystem device = getStorageSystem(_dbClient, profile.getSystemId()); if (SupportedProvisioningTypes.NONE.toString().equalsIgnoreCase( device.getSupportedProvisioningType())) { _logger.info("Storage System doesn't support volume creations :" + device.getSerialNumber()); return; } Set<String> protocols = (Set<String>) keyMap.get(Constants.PROTOCOLS); Map<URI, StoragePool> poolsToMatchWithVpool = (Map<URI, StoragePool>) keyMap.get(Constants.MODIFIED_STORAGEPOOLS); while (it.hasNext()) { CIMInstance poolInstance = null; try { poolInstance = it.next(); // Supporting both thick and thin pools String[] poolClassNameAndSupportedVolumeTypes = determinePoolClassNameAndSupportedVolumeTypes(poolInstance, device); if (null != poolClassNameAndSupportedVolumeTypes) { String instanceID = getCIMPropertyValue(poolInstance, Constants.INSTANCEID); addPath(keyMap, operation.getResult(), poolInstance.getObjectPath()); StoragePool pool = checkStoragePoolExistsInDB( getNativeIDFromInstance(instanceID), _dbClient, device); createStoragePool(pool, poolInstance, profile, poolClassNameAndSupportedVolumeTypes[0], poolClassNameAndSupportedVolumeTypes[1], protocols, poolsToMatchWithVpool, device); if (DiscoveredDataObject.Type.vnxblock.toString().equalsIgnoreCase(device.getSystemType())) { addPath(keyMap, Constants.VNXPOOLS, poolInstance.getObjectPath()); } if (DiscoveredDataObject.Type.vmax.toString().equalsIgnoreCase(device.getSystemType())) { addPath(keyMap, Constants.VMAXPOOLS, poolInstance.getObjectPath()); if (!device.checkIfVmax3()) { addPath(keyMap, Constants.VMAX2POOLS, poolInstance.getObjectPath()); } } // This approach deviates from the existing built plugin framework for plugin // Discovery // To follow the existing pattern, we need to have different SMI-S calls // 1st to get Device StoragePools alone ,and 2nd to get Thin Pools. // Its a tradeoff between whether to go with the current plugin design or // reduce the number of calls to SMI Provider. // I chose the 2nd option. if (!poolClassNameAndSupportedVolumeTypes[0].contains(DEVICE_STORAGE_POOL)) { addPath(keyMap, Constants.THINPOOLS, poolInstance.getObjectPath()); } addPath(keyMap, Constants.DEVICEANDTHINPOOLS, poolInstance.getObjectPath()); } else { _logger.debug("Skipping Pools other than Unified & Virtual & Device : {}", poolInstance.getObjectPath().toString()); } } catch (Exception e) { _logger.warn("StoragePool Discovery failed for {}", getCIMPropertyValue(poolInstance, Constants.INSTANCEID), e); } } _dbClient.createObject(_newPoolList); _dbClient.updateAndReindexObject(_updatePoolList); // find the pools not visible in this discovery List<StoragePool> discoveredPools = new ArrayList<StoragePool>(_newPoolList); discoveredPools.addAll(_updatePoolList); List<StoragePool> notVisiblePools = DiscoveryUtils.checkStoragePoolsNotVisible(discoveredPools, _dbClient, device.getId()); for (StoragePool notVisiblePool : notVisiblePools) { poolsToMatchWithVpool.put(notVisiblePool.getId(), notVisiblePool); } // If any storage ports on the storage system are in a transport // zone, there is an implicit connection to the transport zone // varray. We need to add these implicit varray // connections for the new storage pool. StoragePoolAssociationHelper.setStoragePoolVarrays(device.getId(), _newPoolList, _dbClient); } catch (Exception e) { _logger.error("StoragePool Discovery failed --> {}", getMessage(e)); } finally { _newPoolList = null; _updatePoolList = null; } } /** * Include only Unified,Virtual [Thin] and Device Storage Pools (Thick Pool) * * @param poolInstance * @return String [] array of pool class name (as a first element) and supported volume types (as a second element) */ private String[] determinePoolClassNameAndSupportedVolumeTypes(CIMInstance poolInstance, StorageSystem system) { if (StoragePool.PoolClassNames.Clar_DeviceStoragePool.toString(). equalsIgnoreCase(poolInstance.getClassName())) { return new String[] { StoragePool.PoolClassNames.Clar_DeviceStoragePool.toString(), StoragePool.SupportedResourceTypes.THICK_ONLY.toString() }; } else if (StoragePool.PoolClassNames.Clar_UnifiedStoragePool.toString(). equalsIgnoreCase(poolInstance.getClassName())) { return new String[] { StoragePool.PoolClassNames.Clar_UnifiedStoragePool.toString(), StoragePool.SupportedResourceTypes.THIN_AND_THICK.toString() }; } if (!system.checkIfVmax3()) { if (StoragePool.PoolClassNames.Symm_DeviceStoragePool.toString(). equalsIgnoreCase(poolInstance.getClassName()) && !SupportedProvisioningTypes.THIN.toString().equalsIgnoreCase(system.getSupportedProvisioningType())) { return new String[] { StoragePool.PoolClassNames.Symm_DeviceStoragePool.toString(), StoragePool.SupportedResourceTypes.THICK_ONLY.toString() }; } else if (StoragePool.PoolClassNames.Symm_VirtualProvisioningPool.toString(). equalsIgnoreCase(poolInstance.getClassName()) && !SupportedProvisioningTypes.THICK.toString().equalsIgnoreCase(system.getSupportedProvisioningType())) { return new String[] { StoragePool.PoolClassNames.Symm_VirtualProvisioningPool.toString(), StoragePool.SupportedResourceTypes.THIN_ONLY.toString() }; } } else { // VMAX3 has StorageResourcePools (SRP). These are composed of ThinPools, which we can // discover, but would not have write access to. So, we will only discovery SRP pools // and skip over other pool discoveries. if (StoragePool.PoolClassNames.Symm_SRPStoragePool.toString(). equalsIgnoreCase(poolInstance.getClassName())) { return new String[] { StoragePool.PoolClassNames.Symm_SRPStoragePool.toString(), StoragePool.SupportedResourceTypes.THIN_ONLY.toString() }; } } return null; } /** * Create StoragePool Record, if not present already, else update only the properties. * * @param pool * @param poolInstance * @param profile * @param poolClassName * @param supportedVolumeTypes * @param protocols * @param poolsToMatchWithVpool * @throws URISyntaxException * @throws IOException */ private void createStoragePool(StoragePool pool, CIMInstance poolInstance, AccessProfile profile, String poolClassName, String supportedVolumeTypes, Set<String> protocols, Map<URI, StoragePool> poolsToMatchWithVpool, StorageSystem device) throws URISyntaxException, IOException { boolean newPool = false; boolean modifiedPool = false; // indicates whether to add to modified pools list or not if (null == pool) { String instanceID = getCIMPropertyValue(poolInstance, Constants.INSTANCEID); String nativeIdFromInstance = getNativeIDFromInstance(instanceID); newPool = true; pool = new StoragePool(); pool.setId(URIUtil.createId(StoragePool.class)); pool.setPoolName(getCIMPropertyValue(poolInstance, POOL_ID)); pool.setNativeId(nativeIdFromInstance); pool.setStorageDevice(profile.getSystemId()); pool.setPoolServiceType(PoolServiceType.block.toString()); String poolNativeGuid = NativeGUIDGenerator.generateNativeGuid(_dbClient, pool); pool.setNativeGuid(poolNativeGuid); pool.setLabel(poolNativeGuid); // setting default values on Pool Creation for VMAX and VNX pool.setMaximumThickVolumeSize(0L); pool.setMinimumThickVolumeSize(0L); pool.setMaximumThinVolumeSize(0L); pool.setMinimumThinVolumeSize(0L); if (device.getAutoTieringEnabled()) { pool.setAutoTieringEnabled(Boolean.TRUE); } else { pool.setAutoTieringEnabled(Boolean.FALSE); } _logger.info(String.format("Maximum default limits for volume capacity in storage pool %s / %s : \n " + "max thin volume capacity: %s, max thick volume capacity: %s ", pool.getPoolName(), pool.getId(), pool.getMaximumThinVolumeSize(), pool.getMaximumThickVolumeSize())); } String maxSubscriptionPercent = getCIMPropertyValue(poolInstance, SmisConstants.CP_EMCMAXSUBSCRIPTIONPERCENT); _logger.info(String.format("Discovered maximum subscription percent of storage pool %s from array : %s ", pool.getPoolName(), maxSubscriptionPercent)); // null,0 values indicate "not available". Integer newMaxSubscriptionPercentFromArray = maxSubscriptionPercent == null ? null : new Integer(maxSubscriptionPercent); _logger.info(String.format("New maximum subscription percent of storage pool %s from array : %s ", pool.getPoolName(), newMaxSubscriptionPercentFromArray)); processMaxSubscriptionPercent(newMaxSubscriptionPercentFromArray, pool); _logger.info(String.format("StoragePool %s subscription/utilization percent limits after processing: %s / %s", pool.getPoolName(), pool.getMaxThinPoolSubscriptionPercentage(), pool.getMaxPoolUtilizationPercentage())); String subscribedCapacity = getCIMPropertyValue(poolInstance, SmisConstants.CP_SUBSCRIBEDCAPACITY); if (null != subscribedCapacity) { pool.setSubscribedCapacity(ControllerUtils.convertBytesToKBytes(subscribedCapacity)); } pool.setFreeCapacity(SmisUtils.getFreeCapacity(poolInstance)); pool.setTotalCapacity(SmisUtils.getTotalCapacity(poolInstance)); pool.setPoolClassName(poolClassName); pool.setSupportedResourceTypes(supportedVolumeTypes); String operationalStatus = determineOperationalStatus(poolInstance); if (!newPool && (ImplicitPoolMatcher.checkPoolPropertiesChanged(pool.getOperationalStatus(), operationalStatus) || ImplicitPoolMatcher.checkPoolPropertiesChanged(pool.getProtocols(), protocols) || ImplicitPoolMatcher.checkPoolPropertiesChanged(pool.getCompatibilityStatus(), DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name()) || ImplicitPoolMatcher.checkPoolPropertiesChanged(pool.getDiscoveryStatus(), DiscoveredDataObject.DiscoveryStatus.VISIBLE.name()))) { modifiedPool = true; } pool.addProtocols(protocols); pool.setOperationalStatus(operationalStatus); pool.setCompatibilityStatus(DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name()); pool.setDiscoveryStatus(DiscoveredDataObject.DiscoveryStatus.VISIBLE.name()); if (Constants.ENABLED.toString().equalsIgnoreCase(getCIMPropertyValue(poolInstance, VP_COMPRESSION_STATE))) { pool.setCompressionEnabled(Boolean.TRUE); } else { pool.setCompressionEnabled(Boolean.FALSE); } Set<String> diskDrives = new HashSet<String>(); String driveTypes = getCIMPropertyValue(poolInstance, EMC_DRIVE_TYPE); if (null != driveTypes) { String driveTypesArr[] = driveTypes.split(SPACE_STR_DELIM); if (device.checkIfVmax3() && driveTypesArr.length == 1 && driveTypesArr[0].equals(MIXED_DRIVE_TYPE)) { driveTypesArr = getVMAX3PoolDriveTypes(device, poolInstance); } for (String driveType : driveTypesArr) { String driveDisplayName = SupportedDriveTypeValues.getDiskDriveDisplayName(driveType); if (null == driveDisplayName) { _logger.warn( "UnSupported DiskDrive Type : {} resulting in drives not getting discovered for this pool: {}", driveType, getCIMPropertyValue(poolInstance, Constants.INSTANCEID)); continue; } diskDrives.add(driveDisplayName); } if (!newPool && !modifiedPool && ImplicitPoolMatcher.checkPoolPropertiesChanged(pool.getSupportedDriveTypes(), diskDrives)) { modifiedPool = true; } pool.addDriveTypes(diskDrives); } _logger.info("Discovered disk drives:[{}] for pool id:{}", driveTypes, pool.getId()); if (newPool) { _newPoolList.add(pool); // add new pools to modified pools list to consider them for implicit pool matching. if (!poolsToMatchWithVpool.containsKey(pool.getId())) { poolsToMatchWithVpool.put(pool.getId(), pool); } } else { _updatePoolList.add(pool); // add to modified pool list if pool's property which is required for vPool matcher, has changed. // No need to check whether the pool is already there in the list here // because this processor is the first to discover pools. if (modifiedPool && !poolsToMatchWithVpool.containsKey(pool.getId())) { poolsToMatchWithVpool.put(pool.getId(), pool); } } } private void processMaxSubscriptionPercent(Integer newMaxSubscriptionPercentFromArray, StoragePool pool) { // get default limits from coordinator int maxSubscriptionPercent = (int)CapacityMatcher.getMaxPoolSubscriptionPercentage(pool, _coordinator); int maxUtilizationPercent = (int)CapacityMatcher.getMaxPoolUtilizationPercentage(pool, _coordinator); _logger.info(String.format("Default max subscription/utilization percent limits in vipr: %s / %s", maxSubscriptionPercent, maxUtilizationPercent)); // get pool limits in vipr int poolSubscriptionPercent = pool.getMaxThinPoolSubscriptionPercentage() == null ? 0 : pool.getMaxThinPoolSubscriptionPercentage(); int poolUtilizationPercent = pool.getMaxPoolUtilizationPercentage() == null ? 0 : pool.getMaxPoolUtilizationPercentage(); _logger.info(String.format("StoragePool %s subscription/utilization percent limits in vipr: %s / %s", pool.getPoolName(), poolSubscriptionPercent, poolUtilizationPercent)); // check if we need to set pool max limits based on array limit for max subscription if (isArrayLimitDefined(newMaxSubscriptionPercentFromArray)) { // array limit is defined if(poolSubscriptionPercent == 0 && newMaxSubscriptionPercentFromArray < maxSubscriptionPercent) { // array limit is less than system default limit pool.setMaxThinPoolSubscriptionPercentage(newMaxSubscriptionPercentFromArray); } if(poolUtilizationPercent == 0 && newMaxSubscriptionPercentFromArray < maxUtilizationPercent) { // array limit is less than system default limit pool.setMaxPoolUtilizationPercentage(newMaxSubscriptionPercentFromArray); } } Integer currentMaxSubscriptionPercentFromArray = pool.getMaxThinPoolSubscriptionPercentageFromArray(); _logger.info(String.format("Current maximum subscription percent of storage pool %s from array in vipr : %s ", pool.getPoolName(), currentMaxSubscriptionPercentFromArray)); // Currently smis uses value of 0 as indication that MaxSubscriptionPercent is not defined. // Some array clients explicitly set this array limit to 0 to indicate that the value is 0%. // The OPT was filed 448553 and it targeted for 4.6.2 // Based on the OPT resolution, we use both, 0 and null, values as indication that the property is not defined. if (isArrayLimitDefined(newMaxSubscriptionPercentFromArray) && newMaxSubscriptionPercentFromArray < poolSubscriptionPercent) { // reset vipr limit and send alert pool.setMaxThinPoolSubscriptionPercentage(newMaxSubscriptionPercentFromArray); recordBourneStoragePoolEvent(RecordableEventManager.EventType.StoragePoolUpdated, pool, "Discovered pool max subscription percent is below current pool subscription limit. The limit will be reset.", RecordType.Alert, _dbClient, _eventManager); // check if we need to reset max utilization percent in vipr // pool max utilization percent is always less or equal to pool max subscription percent, // so we do this check in this 'if' statement if (newMaxSubscriptionPercentFromArray < poolUtilizationPercent) { // reset vipr utilization limit and send alert pool.setMaxPoolUtilizationPercentage(newMaxSubscriptionPercentFromArray); recordBourneStoragePoolEvent(RecordableEventManager.EventType.StoragePoolUpdated, pool, "Discovered pool max subscription percent is below current pool utilization limit. The limit will be reset.", RecordType.Alert, _dbClient, _eventManager); } } else if (isArrayLimitDefined(currentMaxSubscriptionPercentFromArray) && currentMaxSubscriptionPercentFromArray == poolSubscriptionPercent && (!isArrayLimitDefined(newMaxSubscriptionPercentFromArray)|| currentMaxSubscriptionPercentFromArray < newMaxSubscriptionPercentFromArray)) { // In this case array limit went up from previous value and max pool subscription percent is using old array value --- // send event that array value was increased so client may increase vipr limits if needed. recordBourneStoragePoolEvent(RecordableEventManager.EventType.StoragePoolUpdated, pool, "Discovered pool max subscription percent is above current pool subscription limit", RecordType.Event, _dbClient, _eventManager); } // set array subscription percent in the pool pool.setMaxThinPoolSubscriptionPercentageFromArray(newMaxSubscriptionPercentFromArray); } private boolean isArrayLimitDefined(Integer maxSubscriptionPercentFromArray) { return (maxSubscriptionPercentFromArray != null && maxSubscriptionPercentFromArray != 0); } /* * determine Operational Status of Pool * READY , if any of the status Value is 2 (OK). * otherwise NOTREADY */ private String determineOperationalStatus(CIMInstance poolInstance) { String operationalStatus = StoragePool.PoolOperationalStatus.NOTREADY.toString(); try { UnsignedInteger16[] opStatus = (UnsignedInteger16[]) poolInstance .getPropertyValue(OPERATIONAL_STATUS); for (UnsignedInteger16 status : opStatus) { if (status.compareTo(new UnsignedInteger16(TWO)) == 0) { operationalStatus = StoragePool.PoolOperationalStatus.READY .toString(); } } } catch (Exception ex) { _logger.error("Discovering Pool Operational Status failed : {}-->", getCIMPropertyValue(poolInstance, Constants.INSTANCEID), ex); } return operationalStatus; } @Override protected void setPrerequisiteObjects(List<Object> inputArgs) throws BaseCollectionException { // TODO Auto-generated method stub } /** * Record storage pool alert/event * * @param storagePoolEventType * @param pool * @param description * @param eventType */ private static void recordBourneStoragePoolEvent(RecordableEventManager.EventType storagePoolEventType, StoragePool pool, String description, RecordType eventType, DbClient dbClient, RecordableEventManager eventManager) { RecordableBourneEvent event = ControllerUtils.convertToRecordableBourneEvent(pool, storagePoolEventType.toString(), description, "", dbClient, ControllerUtils.BLOCK_EVENT_SERVICE, eventType.toString(), ControllerUtils.BLOCK_EVENT_SOURCE); try { eventManager.recordEvents(event); _logger.info("ViPR {} event recorded. Description: {}", event.getType(), event.getDescription()); } catch (Exception ex) { _logger.error(String.format("Failed to record event %s. Event description: %s.", event.getType(), event.getDescription()), ex); } } private String[] getVMAX3PoolDriveTypes(StorageSystem storageDevice, CIMInstance poolInstance) { Set<String> driveTypes = new HashSet<String>(); CloseableIterator<CIMInstance> virtualProvisioningPoolItr = null; _logger.info("Trying to get all VirtualProvisioningPools for storage pool {}", poolInstance.getProperty(SmisConstants.CP_INSTANCE_ID).toString()); CIMObjectPath poolPath = poolInstance.getObjectPath(); try { virtualProvisioningPoolItr = getAssociatorInstances(poolPath, null, SmisConstants.SYMM_VIRTUAL_PROVISIONING_POOL, null, null, SmisConstants.PS_V3_VIRTUAL_PROVISIONING_POOL_PROPERTIES); while (virtualProvisioningPoolItr != null && virtualProvisioningPoolItr.hasNext()) { CIMInstance virtualProvisioningPoolInstance = virtualProvisioningPoolItr.next(); String diskDriveType = CIMPropertyFactory.getPropertyValue(virtualProvisioningPoolInstance, SmisConstants.CP_DISK_DRIVE_TYPE); if (diskDriveType != null) { driveTypes.add(diskDriveType); } } } catch (WBEMException e) { _logger.error("Error getting VirtualProvisioningPools", e); } finally { if (virtualProvisioningPoolItr != null) { virtualProvisioningPoolItr.close(); } } String[] driveTypesArr = driveTypes.toArray(new String[driveTypes.size()]); return driveTypesArr; } public CloseableIterator<CIMInstance> getAssociatorInstances(CIMObjectPath path, String assocClass, String resultClass, String role, String resultRole, String[] prop) throws WBEMException { return _cimClient.associatorInstances(path, null, resultClass, null, null, false, prop); } }