/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.vnxunity;
import java.io.IOException;
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.constraint.URIQueryResultList;
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.HostInterface.Protocol;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.plugins.AccessProfile;
import com.emc.storageos.plugins.common.Constants;
import com.emc.storageos.plugins.common.PartitionManager;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.vnxe.VNXeApiClient;
import com.emc.storageos.vnxe.VNXeApiClientFactory;
import com.emc.storageos.vnxe.models.BlockHostAccess;
import com.emc.storageos.vnxe.models.HostLun;
import com.emc.storageos.vnxe.models.VNXeBase;
import com.emc.storageos.vnxe.models.VNXeHost;
import com.emc.storageos.vnxe.models.VNXeHostInitiator;
import com.emc.storageos.vnxe.models.VNXeLun;
import com.emc.storageos.vnxe.models.VNXeHostInitiator.HostInitiatorTypeEnum;
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;
/**
* Discover array affinity
*
*/
public class VNXUnityArrayAffinityDiscoverer {
private static final Logger logger = LoggerFactory.getLogger(VNXUnityArrayAffinityDiscoverer.class);
private static final String HOST = "Host";
private static final int BATCH_SIZE = 100;
private VNXeApiClientFactory vnxeApiClientFactory;
public void setVnxeApiClientFactory(VNXeApiClientFactory vnxeApiClientFactory) {
this.vnxeApiClientFactory = vnxeApiClientFactory;
}
/**
* Discover array affinity
* @param accessProfile AccesssProfile
* @param dbClient DbClient
* @param partitionManager PartitionManager
* @throws Exception
*/
public void discoverArrayAffinity(AccessProfile accessProfile, DbClient dbClient,
PartitionManager partitionManager) throws Exception {
logger.info("Started array affinity discovery for system {}", accessProfile.getSystemId());
VNXeApiClient apiClient = vnxeApiClientFactory.getUnityClient(accessProfile.getIpAddress(),
accessProfile.getPortNumber(), accessProfile.getUserName(),
accessProfile.getPassword());
StorageSystem system = dbClient.queryObject(StorageSystem.class, accessProfile.getSystemId());
String hostIdsStr = accessProfile.getProps().get(Constants.HOST_IDS);
if (hostIdsStr != null) {
// array affinity for a host
logger.info("Processing hosts {}", hostIdsStr);
String[] hostIds = hostIdsStr.split(Constants.ID_DELIMITER);
for (String hostId : hostIds) {
logger.info("Processing host {}", hostId);
processHost(system, apiClient, dbClient, hostId);
}
} else {
// array affinity for all hosts
logger.info("Processing all hosts");
processAllHosts(system, apiClient, dbClient, partitionManager);
}
}
/**
* Update preferredPools of a host
* @param system
* @param apiClient
* @param dbClient
* @param hostIdStr
*/
private void processHost(StorageSystem system, VNXeApiClient apiClient, DbClient dbClient, String hostIdStr) {
Set<String> systemIds = new HashSet<String>();
systemIds.add(system.getId().toString());
try {
Host host = dbClient.queryObject(Host.class, URI.create(hostIdStr));
if (host != null && !host.getInactive()) {
Map<String, String> preferredPoolURIs = getPreferredPoolMap(system, host.getId(), apiClient, dbClient);
if (ArrayAffinityDiscoveryUtils.updatePreferredPools(host, systemIds, dbClient, preferredPoolURIs)) {
dbClient.updateObject(host);
}
}
} catch (Exception e) {
logger.warn("Exception on updatePreferredSystem", e);
}
}
/**
* Construct pool to pool type map for a host
*
* @param system
* @param hostId
* @param apiClient
* @param dbClient
* @return pool to pool type map
* @throws IOException
*/
private Map<String, String> getPreferredPoolMap(StorageSystem system, URI hostId, VNXeApiClient apiClient, DbClient dbClient)
throws IOException {
Map<String, String> preferredPoolMap = new HashMap<String, String>();
Map<String, StoragePool> pools = getStoragePoolMap(system, dbClient);
List<Initiator> allInitiators = CustomQueryUtility
.queryActiveResourcesByConstraint(dbClient,
Initiator.class, ContainmentConstraint.Factory
.getContainedObjectsConstraint(
hostId,
Initiator.class, Constants.HOST));
String vnxeHostId = null;
for (Initiator initiator : allInitiators) {
logger.info("Processing initiator {}", initiator.getLabel());
String initiatorId = initiator.getInitiatorPort();
if (Protocol.FC.name().equals(initiator.getProtocol())) {
initiatorId = initiator.getInitiatorNode() + ":" + initiatorId;
}
// query VNX Unity initiator
VNXeHostInitiator vnxeInitiator = apiClient.getInitiatorByWWN(initiatorId);
if (vnxeInitiator != null) {
VNXeBase parentHost = vnxeInitiator.getParentHost();
if (parentHost != null) {
vnxeHostId = parentHost.getId();
break;
}
}
}
if (vnxeHostId == null) {
logger.info("Host {} cannot be found on array", hostId);
return preferredPoolMap;
}
// Get vnxeHost from vnxeHostId
VNXeHost vnxeHost = apiClient.getHostById(vnxeHostId);
List<VNXeBase> hostLunIds = vnxeHost.getHostLUNs();
if (hostLunIds != null && !hostLunIds.isEmpty()) {
for (VNXeBase hostLunId : hostLunIds) {
HostLun hostLun = apiClient.getHostLun(hostLunId.getId());
// get lun from from hostLun
VNXeBase lunId = hostLun.getLun();
if (lunId != null) {
VNXeLun lun = apiClient.getLun(lunId.getId());
if (lun != null) {
String nativeGuid = NativeGUIDGenerator.generateNativeGuidForVolumeOrBlockSnapShot(
system.getNativeGuid(), lun.getId());
if (DiscoveryUtils.checkStorageVolumeExistsInDB(dbClient, nativeGuid) != null) {
logger.info("Skipping volume {} as it is already managed by ViPR", nativeGuid);
continue;
}
StoragePool pool = getStoragePoolOfUnManagedObject(lun.getPool().getId(), system, pools);
if (pool != null) {
String exportType = isSharedLun(lun) ? ExportGroup.ExportGroupType.Cluster.name()
: ExportGroup.ExportGroupType.Host.name();
ArrayAffinityDiscoveryUtils.addPoolToPreferredPoolMap(preferredPoolMap, pool.getId().toString(), exportType);
} else {
logger.error("Skipping volume {} as its storage pool doesn't exist in ViPR",
lun.getId());
}
}
}
}
}
return preferredPoolMap;
}
/**
* Check if a LUN is shared
* @param lun
* @return boolean true if the LUN is shared
*/
private boolean isSharedLun(VNXeLun lun) {
List<BlockHostAccess> accesses = lun.getHostAccess();
int hostCount = 0;
if (accesses != null && !accesses.isEmpty()) {
for (BlockHostAccess access : accesses) {
if (access != null) {
VNXeBase hostId = access.getHost();
if (hostId != null) {
hostCount++;
if (hostCount > 1) {
return true;
}
}
}
}
}
return false;
}
/**
* Process all hosts in ViPR DB
* @param system
* @param apiClient
* @param dbClient
* @param partitionManager
*/
private void processAllHosts(StorageSystem system, VNXeApiClient apiClient, DbClient dbClient, PartitionManager partitionManager) {
Map<URI, List<String>> hostToVolumesMap = new HashMap<URI, List<String>>();
Map<String, URI> volumeToPoolMap = new HashMap<String, URI>();
Map<String, URI> hostIdToHostURIMap = new HashMap<String, URI>();
Map<String, Set<URI>> volumeToHostsMap = new HashMap<String, Set<URI>>();
Set<String> systemIds = new HashSet<String>();
systemIds.add(system.getId().toString());
try {
processAllLuns(system, apiClient, dbClient, hostToVolumesMap, volumeToHostsMap, volumeToPoolMap, hostIdToHostURIMap);
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()) {
logger.info("Processing host {}", host.getLabel());
Map<String, String> preferredPoolMap = new HashMap<String, String>();
// check volumes
List<String> volumes = hostToVolumesMap.get(host.getId());
if (volumes != null && !volumes.isEmpty()) {
for (String volume : volumes) {
URI pool = volumeToPoolMap.get(volume);
if (pool != null) {
String exportType = volumeToHostsMap.get(volume).size() > 1 ? ExportGroup.ExportGroupType.Cluster.name()
: ExportGroup.ExportGroupType.Host.name();
ArrayAffinityDiscoveryUtils.addPoolToPreferredPoolMap(preferredPoolMap, pool.toString(), exportType);
}
}
}
if (ArrayAffinityDiscoveryUtils.updatePreferredPools(host, systemIds, 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 processAllHosts", e);
}
}
/**
* Discover array affinity via LUNs
*
* @param system
* @param apiClient
* @param dbClient
* @param hostToVolumesMap
* @param volumeToHostsMap
* @param volumeToPoolMap
* @param hostIdToHostURIMap
* @throws Exception
*/
private void processAllLuns(StorageSystem system, VNXeApiClient apiClient, DbClient dbClient,
Map<URI, List<String>> hostToVolumesMap, Map<String, Set<URI>> volumeToHostsMap, Map<String, URI> volumeToPoolMap,
Map<String, URI> hostIdToHostURIMap) throws Exception {
List<VNXeLun> luns = apiClient.getAllLuns();
if (luns != null && !luns.isEmpty()) {
Map<String, StoragePool> pools = getStoragePoolMap(system, dbClient);
for (VNXeLun lun : luns) {
String nativeGuid = NativeGUIDGenerator.generateNativeGuidForVolumeOrBlockSnapShot(
system.getNativeGuid(), lun.getId());
if (DiscoveryUtils.checkStorageVolumeExistsInDB(dbClient, nativeGuid) != null) {
logger.info("Skipping volume {} as it is already managed by ViPR", nativeGuid);
continue;
}
StoragePool pool = getStoragePoolOfUnManagedObject(lun.getPool().getId(), system, pools);
if (pool != null) {
// the Lun belong to a ViPR host
Set<URI> hostURIs = getHostURIs(lun, apiClient, dbClient, hostIdToHostURIMap);
volumeToHostsMap.put(lun.getId(), hostURIs);
for (URI hostURI : hostURIs) {
List<String> volumes = hostToVolumesMap.get(hostURI);
if (volumes == null) {
volumes = new ArrayList<String>();
hostToVolumesMap.put(hostURI, volumes);
}
volumes.add(lun.getId());
}
volumeToPoolMap.put(lun.getId(), pool.getId());
} else {
logger.error("Skipping volume {} as its storage pool doesn't exist in ViPR",
lun.getId());
}
}
} else {
logger.info("No luns found on the system: {}", system.getId());
}
}
/**
* Find host URIs that a LUN is exported to
*
* @param lun
* @param apiClient
* @param dbClient
* @param hostIdToHostURIMap
* @return set of host URIs
*/
private Set<URI> getHostURIs(VNXeLun lun, VNXeApiClient apiClient, DbClient dbClient, Map<String, URI> hostIdToHostURIMap) {
Set<URI> hostURIs = new HashSet<URI>();
List<BlockHostAccess> accesses = lun.getHostAccess();
if (accesses != null && !accesses.isEmpty()) {
for (BlockHostAccess access : accesses) {
if (access != null) {
VNXeBase hostId = access.getHost();
if (hostId != null) {
hostURIs.add(getHostURI(apiClient, hostId.getId(), dbClient, hostIdToHostURIMap));
}
}
}
}
return hostURIs;
}
/**
* Find host URI from host Id on array
*
* @param apiClient
* @param hostId
* @param dbClient
* @param hostIdToHostURIMap
* @return host URI or NULL_URI
*/
private URI getHostURI(VNXeApiClient apiClient, String hostId, DbClient dbClient, Map<String, URI> hostIdToHostURIMap) {
if (hostIdToHostURIMap.containsKey(hostId)) {
return hostIdToHostURIMap.get(hostId);
}
VNXeHost vnxeHost = apiClient.getHostById(hostId);
URI hostURI = findHostURI(vnxeHost.getFcHostInitiators(), apiClient, dbClient);
if (hostURI == null) {
hostURI = findHostURI(vnxeHost.getIscsiHostInitiators(), apiClient, dbClient);
}
if (hostURI == null) {
hostURI = NullColumnValueGetter.getNullURI();
}
hostIdToHostURIMap.put(hostId, hostURI);
return hostURI;
}
/**
* Find host URI from host initiators on array
*
* @param initiators
* @param apiClient
* @param dbClient
* @return host URI or null
*/
private URI findHostURI(List<VNXeBase> initiators, VNXeApiClient apiClient, DbClient dbClient) {
if (initiators != null && !initiators.isEmpty()) {
for (VNXeBase init : initiators) {
VNXeHostInitiator vnxeInitiator = apiClient.getHostInitiator(init.getId());
String portwwn = vnxeInitiator.getPortWWN();
if (HostInitiatorTypeEnum.INITIATOR_TYPE_ISCSI.equals(vnxeInitiator.getType())) {
portwwn = vnxeInitiator.getInitiatorId();
}
if (portwwn == null || portwwn.isEmpty()) {
continue;
}
Initiator initiator = NetworkUtil.getInitiator(portwwn, dbClient);
if (initiator != null && !initiator.getInactive()) {
URI hostURI = initiator.getHost();
if (!NullColumnValueGetter.isNullURI(hostURI)) {
return hostURI;
}
}
}
}
return null;
}
/**
* Construct pool's native GUID to storage pool object map
*
* @param storageSystem
* @param dbClient
* @return map of pool's native GUID to storage pool object
*/
private Map<String, StoragePool> getStoragePoolMap(StorageSystem storageSystem, DbClient dbClient) {
URIQueryResultList storagePoolURIs = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory
.getStorageDeviceStoragePoolConstraint(storageSystem.getId()),
storagePoolURIs);
HashMap<String, StoragePool> pools = new HashMap<String, StoragePool>();
Iterator<URI> poolsItr = storagePoolURIs.iterator();
while (poolsItr.hasNext()) {
URI storagePoolURI = poolsItr.next();
StoragePool storagePool = dbClient.queryObject(StoragePool.class, storagePoolURI);
pools.put(storagePool.getNativeGuid(), storagePool);
}
return pools;
}
/**
* Return the pool of the UnManaged volume.
*
* @param storageResource
* @param system
* @param dbClient
* @return storage pool
* @throws IOException
*/
private StoragePool getStoragePoolOfUnManagedObject(String poolNativeId,
StorageSystem system, Map<String, StoragePool> pools) throws IOException {
String poolNativeGuid = NativeGUIDGenerator.generateNativeGuid(system, poolNativeId, NativeGUIDGenerator.POOL);
if (pools.containsKey(poolNativeGuid)) {
return pools.get(poolNativeGuid);
}
return null;
}
}