/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.xtremio;
import java.net.URI;
import java.util.ArrayList;
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 com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
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.model.StoragePool;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.plugins.common.Constants;
import com.emc.storageos.plugins.common.PartitionManager;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator;
import com.emc.storageos.volumecontroller.impl.plugins.discovery.smis.processor.export.ArrayAffinityDiscoveryUtils;
import com.emc.storageos.volumecontroller.impl.utils.DiscoveryUtils;
import com.emc.storageos.volumecontroller.impl.xtremio.prov.utils.XtremIOProvUtils;
import com.emc.storageos.xtremio.restapi.XtremIOClient;
import com.emc.storageos.xtremio.restapi.XtremIOClientFactory;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOInitiator;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOInitiatorGroup;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOLunMap;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOObjectInfo;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOVolume;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* The Class XtremIOArrayAffinityDiscoverer discovers the storage pool information for the host(s) if it has volumes exported to it.
*/
public class XtremIOArrayAffinityDiscoverer {
private static final Logger log = LoggerFactory.getLogger(XtremIOArrayAffinityDiscoverer.class);
private static final String HOST = "Host";
private XtremIOClientFactory xtremioRestClientFactory;
public void setXtremioRestClientFactory(XtremIOClientFactory xtremioRestClientFactory) {
this.xtremioRestClientFactory = xtremioRestClientFactory;
}
/**
* Find and update preferred pools information for the given host.
*
* @param system the system
* @param host the host
* @param dbClient the db client
* @throws Exception the exception
*/
public void findAndUpdatePreferredPoolsForHost(StorageSystem system, Host host, DbClient dbClient) throws Exception {
Map<String, String> preferredPoolMap = getPreferredPoolMapForHost(system, host, dbClient);
if (ArrayAffinityDiscoveryUtils.updatePreferredPools(host, Sets.newHashSet(system.getId().toString()),
dbClient, preferredPoolMap)) {
dbClient.updateObject(host);
}
}
/**
* Gets the preferred pool map for the given host.
*/
private Map<String, String> getPreferredPoolMapForHost(StorageSystem system, Host host, DbClient dbClient) throws Exception {
/**
* Group host's initiators by IG,
* For each IG:
* - Get list of volume maps [List all volume maps and see which ones have mapping for this IG],
* - Get pool detail for each volume,
* - Find the mask type for the pool.
*/
Map<String, String> preferredPoolMap = new HashMap<String, String>();
XtremIOClient xtremIOClient = XtremIOProvUtils.getXtremIOClient(dbClient, system, xtremioRestClientFactory);
String xioClusterName = xtremIOClient.getClusterDetails(system.getSerialNumber()).getName();
List<Initiator> hostInitiators = CustomQueryUtility
.queryActiveResourcesByConstraint(dbClient, Initiator.class,
ContainmentConstraint.Factory.getContainedObjectsConstraint(host.getId(), Initiator.class, Constants.HOST));
ArrayListMultimap<String, Initiator> groupInitiatorsByIG = ArrayListMultimap.create();
for (Initiator initiator : hostInitiators) {
log.info("Processing host initiator {}", initiator.getLabel());
String igName = XtremIOProvUtils.getIGNameForInitiator(initiator, system.getSerialNumber(), xtremIOClient, xioClusterName);
if (igName != null && !igName.isEmpty()) {
groupInitiatorsByIG.put(igName, initiator);
}
}
log.info("List of IGs found {}",
Joiner.on(",").join(groupInitiatorsByIG.asMap().entrySet()));
Set<String> igNames = groupInitiatorsByIG.keySet();
if (!igNames.isEmpty()) {
// map of IG name to Volume names mapped
Map<String, Set<String>> igToVolumesMap = getIgToVolumesMap(xtremIOClient, xioClusterName);
Set<String> volumeNames = getVolumesForHost(igNames, igToVolumesMap);
// consider only unmanaged volumes
filterKnownVolumes(system, dbClient, xtremIOClient, xioClusterName, volumeNames);
// get storage pool for matching volumes
if (!volumeNames.isEmpty()) {
log.info("UnManaged Volumes found for this Host: {}", volumeNames);
// As XtremIO array has only one storage pool, add the pool directly.
// get the storage pool associated with the XtremIO system
StoragePool storagePool = XtremIOProvUtils.getXtremIOStoragePool(system.getId(), dbClient);
if (storagePool != null) {
String maskType = getMaskTypeForHost(xtremIOClient, xioClusterName, groupInitiatorsByIG, null, igNames, volumeNames);
ArrayAffinityDiscoveryUtils.addPoolToPreferredPoolMap(preferredPoolMap, storagePool.getId().toString(), maskType);
}
} else {
log.info("No UnManaged Volumes found for this Host");
}
}
return preferredPoolMap;
}
/**
* Gets the volumes for the given host's IGs.
*/
private Set<String> getVolumesForHost(Set<String> igNames, Map<String, Set<String>> igToVolumesMap) throws Exception {
Set<String> volumeNames = new HashSet<String>();
if (igNames != null) {
log.info("Querying volumes for IGs {}", igNames.toArray());
for (String igName : igNames) {
Set<String> igVolumes = igToVolumesMap.get(igName);
log.info("Volumes {} found for IG {}", igVolumes, igName);
if (igVolumes != null) {
volumeNames.addAll(igVolumes);
}
}
}
return volumeNames;
}
/**
* Queries the lun maps information from array, forms IG name to volume names map.
*/
private Map<String, Set<String>> getIgToVolumesMap(XtremIOClient xtremIOClient, String xioClusterName) throws Exception {
log.info("Querying lun maps for cluster {}", xioClusterName);
Map<String, Set<String>> igToVolumesMap = new HashMap<>();
// get the XtremIO lun map links and process them in batches
List<XtremIOObjectInfo> lunMapLinks = xtremIOClient.getXtremIOLunMapLinks(xioClusterName);
List<List<XtremIOObjectInfo>> lunMapPartitions = Lists.partition(lunMapLinks, Constants.DEFAULT_PARTITION_SIZE);
for (List<XtremIOObjectInfo> partition : lunMapPartitions) {
// Get the lun map details
List<XtremIOLunMap> lunMaps = xtremIOClient.getXtremIOLunMapsForLinks(partition, xioClusterName);
for (XtremIOLunMap lunMap : lunMaps) {
try {
log.info("Looking at lun map {}; IG name: {}, Volume: {}",
lunMap.getMappingInfo().get(2), lunMap.getIgName(), lunMap.getVolumeName());
String igName = lunMap.getIgName();
Set<String> volumes = igToVolumesMap.get(igName);
if (volumes == null) {
volumes = new HashSet<String>();
igToVolumesMap.put(igName, volumes);
}
volumes.add(lunMap.getVolumeName());
} catch (Exception ex) {
log.warn("Error processing XtremIO lun map {}. {}", lunMap, ex.getMessage());
}
}
}
return igToVolumesMap;
}
/**
* Filter the known volumes in DB, as the preferredPools list in Host needs
* to have pools of unmanaged volumes alone.
*/
private void filterKnownVolumes(StorageSystem system, DbClient dbClient,
XtremIOClient xtremIOClient, String xioClusterName, Set<String> igVolumes) throws Exception {
Iterator<String> itr = igVolumes.iterator();
while (itr.hasNext()) {
String volumeName = itr.next();
XtremIOVolume xioVolume = xtremIOClient.getVolumeDetails(volumeName, xioClusterName);
String managedVolumeNativeGuid = NativeGUIDGenerator.generateNativeGuidForVolumeOrBlockSnapShot(
system.getNativeGuid(), xioVolume.getVolInfo().get(0));
Volume dbVolume = DiscoveryUtils.checkStorageVolumeExistsInDB(dbClient, managedVolumeNativeGuid);
if (dbVolume != null) {
itr.remove();
}
}
}
/**
* Identifies the mask type (Host/Cluster) for the given host's IGs.
*
* @param xtremIOClient - xtremio client
* @param xioClusterName - xio cluster name
* @param groupInitiatorsByIG - IG name to initiators map
* @param igNameToHostsMap - IG name to hosts map
* @param hostIGNames - host to IG names map
* @param volumeNames - volume names for the host
* @return the mask type for host
* @throws Exception
*/
private String getMaskTypeForHost(XtremIOClient xtremIOClient, String xioClusterName,
ArrayListMultimap<String, Initiator> groupInitiatorsByIG, Map<String, Set<String>> igNameToHostsMap, Set<String> hostIGNames,
Set<String> volumeNames)
throws Exception {
log.debug("Finding out mask type for the host");
/**
* 1. If any of the host's IG has initiators other than host's initiators, then cluster type. Otherwise, exclusive type.
* 2. Further check: Get Lun mappings from host's volumes, get IG names from each Lun mapping,
* - if volumes are shared with more than the host's IGs, it means it is a shared volume.
*/
String maskType = ExportGroup.ExportGroupType.Host.name();
for (String igName : hostIGNames) {
XtremIOInitiatorGroup xioIG = xtremIOClient.getInitiatorGroup(igName, xioClusterName);
if (Integer.parseInt(xioIG.getNumberOfInitiators()) > groupInitiatorsByIG.get(igName).size()
|| (igNameToHostsMap != null && igNameToHostsMap.get(igName) != null && igNameToHostsMap.get(igName).size() > 1)) {
maskType = ExportGroup.ExportGroupType.Cluster.name();
log.info("This Host has volume(s) shared with multiple hosts");
break;
}
}
if (!ExportGroup.ExportGroupType.Cluster.name().equalsIgnoreCase(maskType)) {
Set<String> volumeIGNames = new HashSet<String>();
for (String volumeName : volumeNames) {
XtremIOVolume xioVolume = xtremIOClient.getVolumeDetails(volumeName, xioClusterName);
for (List<Object> lunMapEntries : xioVolume.getLunMaps()) {
@SuppressWarnings("unchecked")
List<Object> igDetails = (List<Object>) lunMapEntries.get(0);
if (null == igDetails.get(1)) {
log.warn("IG Name is null in returned lun map response for volume {}", volumeName);
continue;
}
String volumeIGName = (String) igDetails.get(1);
volumeIGNames.add(volumeIGName);
}
}
log.info("Host IG names: {}, Volumes IG names: {}", hostIGNames, volumeIGNames);
volumeIGNames.removeAll(hostIGNames);
if (!volumeIGNames.isEmpty()) {
maskType = ExportGroup.ExportGroupType.Cluster.name();
log.info("This Host has volume(s) shared with multiple hosts");
}
}
return maskType;
}
/**
* Find and update preferred pools information for all Hosts.
*
* @param system the system
* @param dbClient the db client
* @param partitionManager
* @throws Exception the exception
*/
public void findAndUpdatePreferredPools(StorageSystem system, DbClient dbClient, PartitionManager partitionManager) throws Exception {
/**
* Get all initiators on array,
* Group initiators by IG, also maintain a map of Host to IGs, and a map of IG to Hosts.
* For each Host in the DB:
* - Find if any of its IG has volumes,
* - Find the mask type for the host.
*/
XtremIOClient xtremIOClient = XtremIOProvUtils.getXtremIOClient(dbClient, system, xtremioRestClientFactory);
String xioClusterName = xtremIOClient.getClusterDetails(system.getSerialNumber()).getName();
// Group all the initiators and their initiator groups based on ViPR host.
ArrayListMultimap<String, Initiator> igNameToInitiatorsMap = ArrayListMultimap.create();
Map<URI, Set<String>> hostToIGNamesMap = new HashMap<URI, Set<String>>();
Map<String, Set<String>> igNameToHostsMap = new HashMap<String, Set<String>>();
List<XtremIOInitiator> initiators = xtremIOClient.getXtremIOInitiatorsInfo(xioClusterName);
for (XtremIOInitiator initiator : initiators) {
String initiatorNetworkId = initiator.getPortAddress();
// check if a host initiator exists for this id
Initiator knownInitiator = NetworkUtil.getInitiator(initiatorNetworkId, dbClient);
if (knownInitiator == null) {
log.debug("Skipping XtremIO initiator {} as it is not found in database", initiatorNetworkId);
continue;
}
URI hostId = knownInitiator.getHost();
String hostName = knownInitiator.getHostName();
if (!NullColumnValueGetter.isNullURI(hostId)) {
log.info("Found a host {}({}) in ViPR for initiator {}",
hostId.toString(), knownInitiator.getHostName(), initiatorNetworkId);
String igName = initiator.getInitiatorGroup().get(1);
igNameToInitiatorsMap.put(igName, knownInitiator);
Set<String> hostIGNames = hostToIGNamesMap.get(hostId);
if (hostIGNames == null) {
hostIGNames = new HashSet<String>();
hostToIGNamesMap.put(hostId, hostIGNames);
}
hostIGNames.add(igName);
Set<String> igHostNames = igNameToHostsMap.get(igName);
if (igHostNames == null) {
igHostNames = new HashSet<String>();
igNameToHostsMap.put(igName, igHostNames);
}
igHostNames.add(hostName);
} else {
log.info("No host in ViPR found configured for initiator {}", initiatorNetworkId);
}
}
log.info("IG name to Initiators Map: {}", Joiner.on(",").join(igNameToInitiatorsMap.asMap().entrySet()));
log.info("IG name to Hosts Map: {}", Joiner.on(",").join(igNameToHostsMap.entrySet()));
log.info("Host to IG names Map: {}", Joiner.on(",").join(hostToIGNamesMap.entrySet()));
// map of IG name to Volume names mapped
Map<String, Set<String>> igToVolumesMap = getIgToVolumesMap(xtremIOClient, xioClusterName);
// As XtremIO array has only one storage pool, add the pool directly.
// get the storage pool associated with the XtremIO system
StoragePool storagePool = XtremIOProvUtils.getXtremIOStoragePool(system.getId(), dbClient);
List<Host> hostsToUpdate = new ArrayList<Host>();
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 && !host.getInactive()) {
log.info("Processing Host {}", host.getLabel());
Map<String, String> preferredPoolMap = new HashMap<String, String>();
Set<String> volumeNames = getVolumesForHost(hostToIGNamesMap.get(host.getId()), igToVolumesMap);
// consider only unmanaged volumes
filterKnownVolumes(system, dbClient, xtremIOClient, xioClusterName, volumeNames);
if (!volumeNames.isEmpty()) {
log.info("UnManaged Volumes found for this Host: {}", volumeNames);
if (storagePool != null) {
String maskType = getMaskTypeForHost(xtremIOClient, xioClusterName,
igNameToInitiatorsMap, igNameToHostsMap, hostToIGNamesMap.get(host.getId()), volumeNames);
ArrayAffinityDiscoveryUtils.addPoolToPreferredPoolMap(preferredPoolMap,
storagePool.getId().toString(), maskType);
}
} else {
log.info("No UnManaged Volumes found for this Host");
}
if (ArrayAffinityDiscoveryUtils.updatePreferredPools(host, Sets.newHashSet(system.getId().toString()),
dbClient, preferredPoolMap)) {
hostsToUpdate.add(host);
}
}
}
if (!hostsToUpdate.isEmpty()) {
partitionManager.updateAndReIndexInBatches(hostsToUpdate, Constants.DEFAULT_PARTITION_SIZE, dbClient, HOST);
}
}
}