/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.plugins.discovery.smis.processor.export;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
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 javax.cim.CIMInstance;
import javax.cim.CIMObjectPath;
import javax.cim.UnsignedInteger32;
import javax.wbem.CloseableIterator;
import javax.wbem.client.EnumerateResponse;
import javax.wbem.client.WBEMClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.WWNUtility;
import com.emc.storageos.db.client.util.iSCSIUtility;
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.PartitionManager;
import com.emc.storageos.plugins.common.Processor;
import com.emc.storageos.plugins.common.domainmodel.Operation;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.volumecontroller.impl.plugins.SMICommunicationInterface;
import com.emc.storageos.volumecontroller.impl.smis.SmisConstants;
/**
* Processor used for retrieving masking constructs and populating data structures for array affinity.
*/
public class ArrayAffinityExportProcessor extends Processor {
private final Logger _logger = LoggerFactory.getLogger(ArrayAffinityExportProcessor.class);
private AccessProfile _profile;
private Map<String, Object> _keyMap;
private DbClient _dbClient;
private List<Object> _args;
private final String ISCSI_PATTERN = "^(iqn|IQN|eui).*$";
private static int MAX_OBJECT_COUNT = 100;
private static final String HOST = "Host";
private static final int BATCH_SIZE = 100;
private Map<URI, Set<String>> _hostToExportMasksMap = null;
private Map<String, Set<URI>> _exportMaskToHostsMap = null;
private Map<String, Set<String>> _maskToVolumesMap = null;
private Map<String, Set<URI>> _maskToStoragePoolsMap = null;
private Map<String, URI> _volumeToStoragePoolMap = null;
private PartitionManager _partitionManager;
/**
* Method for setting the partition manager via injection.
*
* @param partitionManager the partition manager instance
*/
public void setPartitionManager(PartitionManager partitionManager) {
_partitionManager = partitionManager;
}
/**
* Initialize the Processor. Child classes should call
* super.initialize if they want the various convenience getter
* methods to work.
*
* @param operation
* @param resultObj
* @param keyMap
*/
protected void initialize(Operation operation, Object resultObj,
Map<String, Object> keyMap) {
_keyMap = keyMap;
_dbClient = (DbClient) keyMap.get(Constants.dbClient);
_profile = (AccessProfile) keyMap.get(Constants.ACCESSPROFILE);
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.plugins.common.Processor#processResult(com.emc.storageos.plugins.common.domainmodel.Operation,
* java.lang.Object, java.util.Map)
*/
@SuppressWarnings("unchecked")
@Override
public void processResult(Operation operation, Object resultObj,
Map<String, Object> keyMap) throws BaseCollectionException {
initialize(operation, resultObj, keyMap);
CloseableIterator<CIMInstance> it = null;
EnumerateResponse<CIMInstance> response = null;
WBEMClient client = SMICommunicationInterface.getCIMClient(keyMap);
try {
// get lun masking view CIM path
CIMObjectPath path = getObjectPathfromCIMArgument(_args, keyMap);
_logger.info("looking at lun masking view: " + path.toString());
response = (EnumerateResponse<CIMInstance>) resultObj;
processVolumesAndInitiatorsPaths(response.getResponses(), path.toString(), client);
while (!response.isEnd()) {
_logger.info("Processing next Chunk");
response = client.getInstancesWithPath(Constants.MASKING_PATH, response.getContext(),
new UnsignedInteger32(MAX_OBJECT_COUNT));
processVolumesAndInitiatorsPaths(response.getResponses(), path.toString(), client);
}
} catch (Exception e) {
_logger.error("Processing lun maksing view failed", e);
} finally {
if (it != null) {
it.close();
}
wrapUp();
if (response != null) {
try {
client.closeEnumeration(Constants.MASKING_PATH, response.getContext());
} catch (Exception e) {
_logger.debug("Exception occurred while closing enumeration", e);
}
}
}
}
/**
* Gets the Map of host to maskingViewPaths that is being tracked in the keyMap.
*
* @return a Map of host to maskingViewPaths
*/
private Map<URI, Set<String>> getHostToExportMasksMap() {
// find or create the host -> maskingViewPaths tracking data structure in the key map
_hostToExportMasksMap = (Map<URI, Set<String>>) _keyMap.get(Constants.HOST_EXPORT_MASKS_MAP);
if (_hostToExportMasksMap == null) {
_hostToExportMasksMap = new HashMap<URI, Set<String>>();
_keyMap.put(Constants.HOST_EXPORT_MASKS_MAP, _hostToExportMasksMap);
}
return _hostToExportMasksMap;
}
/**
* Gets the Map of maskingViewPath to hosts that is being tracked in the keyMap.
*
* @return a Map of maskingViewPath to hosts
*/
private Map<String, Set<URI>> getExportMaskToHostsMap() {
// find or create the maskingViewPath -> hosts tracking data structure in the key map
_exportMaskToHostsMap = (Map<String, Set<URI>>) _keyMap.get(Constants.EXPORT_MASK_HOSTS_MAP);
if (_exportMaskToHostsMap == null) {
_exportMaskToHostsMap = new HashMap<String, Set<URI>>();
_keyMap.put(Constants.EXPORT_MASK_HOSTS_MAP, _exportMaskToHostsMap);
}
return _exportMaskToHostsMap;
}
/**
* Gets the Map of maskingViewPath to volumes that is being tracked in the keyMap.
*
* @return a Map of maskingViewPath to volumes
*/
private Map<String, Set<String>> getExportMaskToVolumesMap() {
// find or create the maskingViewPath -> volumes tracking data structure in the key map
_maskToVolumesMap = (Map<String, Set<String>>) _keyMap.get(Constants.EXPORT_MASK_VOLUMES_MAP);
if (_maskToVolumesMap == null) {
_maskToVolumesMap = new HashMap<String, Set<String>>();
_keyMap.put(Constants.EXPORT_MASK_VOLUMES_MAP, _maskToVolumesMap);
}
return _maskToVolumesMap;
}
/**
* Gets the Map of maskingViewPath to StoragePools that is being tracked in the keyMap.
*
* @return a Map of maskingViewPath to StoragePools
*/
private Map<String, Set<URI>> getMaskToStoragePoolsMap() {
// find or create the maskingViewPath -> StoragePools tracking data structure in the key map
_maskToStoragePoolsMap = (Map<String, Set<URI>>) _keyMap.get(Constants.EXPORT_MASK_STORAGE_POOLS_MAP);
if (_maskToStoragePoolsMap == null) {
_maskToStoragePoolsMap = new HashMap<String, Set<URI>>();
_keyMap.put(Constants.EXPORT_MASK_STORAGE_POOLS_MAP, _maskToStoragePoolsMap);
}
return _maskToStoragePoolsMap;
}
/**
* Gets the Map of volume to StoragePool that is being tracked in the keyMap.
*
* @return a Map of volume to StoragePool
*/
private Map<String, URI> getVolumeToStoragePoolMap() {
// find or create the volume -> StoragePool tracking data structure in the key map
_volumeToStoragePoolMap = (Map<String, URI>) _keyMap.get(Constants.VOLUME_STORAGE_POOL_MAP);
if (_volumeToStoragePoolMap == null) {
_volumeToStoragePoolMap = new HashMap<String, URI>();
_keyMap.put(Constants.VOLUME_STORAGE_POOL_MAP, _volumeToStoragePoolMap);
}
return _volumeToStoragePoolMap;
}
/**
* Update preferredPools for hosts
*/
protected void wrapUp() {
Integer currentCommandIndex = this.getCurrentCommandIndex(_args);
List maskingViews = (List) _keyMap.get(Constants.MASKING_VIEWS);
_logger.info("ArrayAffinityExportProcessor current index is " + currentCommandIndex);
_logger.info("ArrayAffinityExportProcessor maskingViews size is " + maskingViews.size());
if ((maskingViews != null) && (maskingViews.size() == (currentCommandIndex + 1))) {
_logger.info("this is the last time ArrayAffinityExportProcessor will be called, cleaning up...");
updatePreferredPools();
} else {
_logger.info("no need to wrap up yet...");
}
}
private void processVolumesAndInitiatorsPaths(CloseableIterator<CIMInstance> it, String maskingViewPath, WBEMClient client) {
while (it.hasNext()) {
CIMInstance instance = it.next();
_logger.info("looking at classname: " + instance.getClassName());
switch (instance.getClassName()) {
// process initiators
case SmisConstants.CP_SE_STORAGE_HARDWARE_ID:
String initiatorNetworkId = this.getCIMPropertyValue(instance, SmisConstants.CP_STORAGE_ID);
_logger.info("looking at initiator network id " + initiatorNetworkId);
if (WWNUtility.isValidNoColonWWN(initiatorNetworkId)) {
initiatorNetworkId = WWNUtility.getWWNWithColons(initiatorNetworkId);
_logger.info(" wwn normalized to " + initiatorNetworkId);
} else if (WWNUtility.isValidWWN(initiatorNetworkId)) {
initiatorNetworkId = initiatorNetworkId.toUpperCase();
_logger.info(" wwn normalized to " + initiatorNetworkId);
} else if (initiatorNetworkId.matches(ISCSI_PATTERN)
&& (iSCSIUtility.isValidIQNPortName(initiatorNetworkId) || iSCSIUtility
.isValidEUIPortName(initiatorNetworkId))) {
_logger.info(" iSCSI storage port normalized to " + initiatorNetworkId);
} else {
_logger.warn(" this is not a valid FC or iSCSI network id format, skipping");
continue;
}
// check if a host initiator exists for this id
Initiator knownInitiator = NetworkUtil.getInitiator(initiatorNetworkId, _dbClient);
URI hostId = null;
if (knownInitiator != null) {
_logger.info("Found an initiator in ViPR on host " + knownInitiator.getHostName());
hostId = knownInitiator.getHost();
} else {
_logger.info("No hosts in ViPR found configured for initiator " + initiatorNetworkId);
}
if (hostId == null) {
hostId = NullColumnValueGetter.getNullURI();
}
// add to map of host to export masks, and map of mask to hosts
Set<String> maskingViewPaths = getHostToExportMasksMap().get(hostId);
if (maskingViewPaths == null) {
maskingViewPaths = new HashSet<String>();
_logger.info("Creating mask set for host {}", hostId);
getHostToExportMasksMap().put(hostId, maskingViewPaths);
}
maskingViewPaths.add(maskingViewPath);
Set<URI> hosts = getExportMaskToHostsMap().get(maskingViewPath);
if (hosts == null) {
_logger.info("Initial host count for mask {}", maskingViewPath);
hosts = new HashSet<URI>();
getExportMaskToHostsMap().put(maskingViewPath, hosts);
}
hosts.add(hostId);
break;
// process storage volumes
case _symmvolume:
case _clarvolume:
CIMObjectPath volumePath = instance.getObjectPath();
_logger.info("volumePath is " + volumePath.toString());
URI poolURI = null;
if (ArrayAffinityDiscoveryUtils.isUnmanagedVolume(volumePath, _dbClient)) {
poolURI = ArrayAffinityDiscoveryUtils.getStoragePool(volumePath, client, _dbClient);
if (!NullColumnValueGetter.isNullURI(poolURI)) {
Set<String> volumes = getExportMaskToVolumesMap().get(maskingViewPath);
if (volumes == null) {
volumes = new HashSet<String>();
_logger.info("Creating volume set for mask {}", maskingViewPath);
getExportMaskToVolumesMap().put(maskingViewPath, volumes);
}
volumes.add(volumePath.toString());
Set<URI> pools = getMaskToStoragePoolsMap().get(maskingViewPath);
if (pools == null) {
pools = new HashSet<URI>();
_logger.info("Creating pool set for mask {}", maskingViewPath);
getMaskToStoragePoolsMap().put(maskingViewPath, pools);
}
pools.add(poolURI);
if (!getVolumeToStoragePoolMap().containsKey(volumePath)) {
getVolumeToStoragePoolMap().put(volumePath.toString(), poolURI);
}
}
}
break;
default:
break;
}
}
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.plugins.common.Processor#setPrerequisiteObjects(java.util.List)
*/
@Override
protected void setPrerequisiteObjects(List<Object> inputArgs)
throws BaseCollectionException {
this._args = inputArgs;
}
private void updatePreferredPools() {
Map<URI, Set<String>> hostToExportMasks = getHostToExportMasksMap();
Map<String, Set<URI>> exportToMaskHostCount = getExportMaskToHostsMap();
Map<String, Set<String>> maskToVolumes = getExportMaskToVolumesMap();
Map<String, Set<URI>> maskToStroagePools = getMaskToStoragePoolsMap();
Map<String, URI> volumeToStoragePool = getVolumeToStoragePoolMap();
String systemIdsStr = _profile.getProps().get(Constants.SYSTEM_IDS);
String[] systemIds = systemIdsStr.split(Constants.ID_DELIMITER);
Set<String> systemIdSet = new HashSet<String>(Arrays.asList(systemIds));
List<Host> hostsToUpdate = new ArrayList<Host>();
Map<URI, Set<String>> hostToVolumes = new HashMap<URI, Set<String>>();
Map<String, Set<URI>> volumeToHosts = new HashMap<String, Set<URI>>();
// populate hostToVolumes and volumeToHosts maps
for (Map.Entry<URI, Set<String>> entry : hostToExportMasks.entrySet()) {
URI host = entry.getKey();
for (String mask : entry.getValue()) {
Set<String> volumes = maskToVolumes.get(mask);
if (volumes != null) {
Set<String> hostVols = hostToVolumes.get(host);
if (hostVols == null) {
hostVols = new HashSet<String>();
hostToVolumes.put(host, hostVols);
}
hostVols.addAll(volumes);
for (String volume : volumes) {
Set<URI> hosts = volumeToHosts.get(volume);
if (hosts == null) {
hosts = new HashSet<URI>();
volumeToHosts.put(volume, hosts);
}
hosts.add(host);
}
}
}
}
try {
List<URI> hostURIs = _dbClient.queryByType(Host.class, true);
Iterator<Host> hosts = _dbClient.queryIterativeObjectFields(Host.class, ArrayAffinityDiscoveryUtils.HOST_PROPERTIES, hostURIs);
while (hosts.hasNext()) {
Host host = hosts.next();
if (host != null) {
_logger.info("Processing host {}", host.getLabel());
// check masks
Map<String, String> preferredPoolMap = new HashMap<String, String>();
Set<String> masks = hostToExportMasks.get(host.getId());
if (masks != null && !masks.isEmpty()) {
for (String mask : masks) {
Set<URI> pools = maskToStroagePools.get(mask);
String exportType = exportToMaskHostCount.get(mask).size() > 1 ? ExportGroup.ExportGroupType.Cluster.name()
: ExportGroup.ExportGroupType.Host.name();
if (pools != null && !pools.isEmpty()) {
for (URI pool : pools) {
ArrayAffinityDiscoveryUtils.addPoolToPreferredPoolMap(preferredPoolMap, pool.toString(), exportType);
}
}
}
}
// check volumes
Set<String> volumes = hostToVolumes.get(host.getId());
if (volumes != null && !volumes.isEmpty()) {
for (String volume : volumes) {
URI pool = volumeToStoragePool.get(volume);
if (pool != null) {
String exportType = volumeToHosts.get(volume).size() > 1 ? ExportGroup.ExportGroupType.Cluster.name()
: ExportGroup.ExportGroupType.Host.name();
ArrayAffinityDiscoveryUtils.addPoolToPreferredPoolMap(preferredPoolMap, pool.toString(), exportType);
}
}
}
if (ArrayAffinityDiscoveryUtils.updatePreferredPools(host, systemIdSet, _dbClient, preferredPoolMap)) {
hostsToUpdate.add(host);
}
}
// if hostsToUpdate size reaches BATCH_SIZE, persist to db
if (hostsToUpdate.size() >= BATCH_SIZE) {
_partitionManager.updateInBatches(hostsToUpdate, BATCH_SIZE, _dbClient, HOST);
hostsToUpdate.clear();
}
}
if (!hostsToUpdate.isEmpty()) {
_partitionManager.updateInBatches(hostsToUpdate, BATCH_SIZE, _dbClient, HOST);
}
} catch (Exception e) {
_logger.warn("Exception on updatePreferredPools", e);
}
}
}