/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.utils.attrmatchers;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.emc.storageos.services.util.StorageDriverManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.DiscoveredSystemObject;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StorageProtocol;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.StorageProtocol.Block;
import com.emc.storageos.db.client.model.StorageProtocol.Transport;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.volumecontroller.AttributeMatcher;
import com.emc.storageos.volumecontroller.impl.plugins.metering.smis.processor.PortMetricsProcessor;
import com.google.common.collect.Sets;
/**
* This matcher checks that if the VirtualPool.num_paths attribute is set,
* then a StoragePool is only matched if the array containing it has at least
* as many "usable" ports of the designated transport type(s) as the num_paths variable.
*
* This is a somewhat inexact test. We cannot guarantee that an Export will succeed
* even with this check, because we don't know the distribution of initiators in the
* Export to Networks. On the other hand, this check can cause Pools to be excluded
* that could be used in certain circumstances because the user actually used fewer
* initiator than num_paths.
*
* What this matcher guarantees is that there are at least as many "usable" ports
* on the array as max_paths. "Usable" is defined in a comment below.
*
* @author watson
*
*/
public class NumPathsMatcher extends AttributeMatcher {
private static final Logger _logger = LoggerFactory
.getLogger(NumPathsMatcher.class);
private static volatile PortMetricsProcessor _portMetricsProcessor;
public void setPortMetricsProcessor(PortMetricsProcessor portMetricsProcessor) {
if (_portMetricsProcessor == null) {
_portMetricsProcessor = portMetricsProcessor;
}
}
@Override
protected boolean isAttributeOn(Map<String, Object> attributeMap) {
return (null != attributeMap && attributeMap.containsKey(Attributes.max_paths
.toString()));
}
/**
* Filters out all pools hosted by arrays that have fewer "usable" ports of the
* designated transport type(s) than the num_paths attribute.
*/
@Override
protected List<StoragePool> matchStoragePoolsWithAttributeOn(
List<StoragePool> allPools, Map<String, Object> attributeMap,
StringBuffer errorMessage) {
boolean checkIP = false;
boolean checkFC = false;
// If not a block vpool, then everything matches.
if (false == attributeMap.get(Attributes.vpool_type.toString())
.equals(VirtualPool.Type.block.name())) {
return allPools;
}
// If Vplex high availability is used, then do not filter on maxPaths because
// the VPlex itself contains no pools, and the underlying array pools should match
// regardless of the maxPath settings.
Object highAvailabilityType = attributeMap.get(Attributes.high_availability_type.toString());
if (highAvailabilityType != null
&& NullColumnValueGetter.isNotNullValue(highAvailabilityType.toString())) {
return allPools;
}
// If protocols is not specified, can't determine which type of ports to check.
Set<String> protocols = (Set<String>) attributeMap.get(Attributes.protocols.toString());
Set<String> vArrays = (Set<String>) attributeMap.get(Attributes.varrays.toString());
if (protocols != null && protocols.contains(Block.FC.name())) {
checkFC = true;
}
if (protocols != null && protocols.contains(Block.iSCSI.name())) {
checkIP = true;
}
Integer maxPaths = (Integer) attributeMap.get(Attributes.max_paths.toString());
Map<URI, Integer> cachedUsableFCPorts = new HashMap<URI, Integer>();
Map<URI, Integer> cachedUsableIPPorts = new HashMap<URI, Integer>();
Map<URI, Integer> cachedUsableFCHADomains = new HashMap<URI, Integer>();
Map<URI, Integer> cachedUsableIPHADomains = new HashMap<URI, Integer>();
List<StoragePool> matchedPools = new ArrayList<StoragePool>();
Map<URI, StorageSystem> storageSystemMap = new HashMap<URI, StorageSystem>();
for (StoragePool pool : allPools) {
URI dev = pool.getStorageDevice();
StorageSystem system = getStorageSystem(storageSystemMap, pool);
if (checkFC) {
if (numberOfUsablePorts(dev, Transport.FC, vArrays, cachedUsableFCPorts, cachedUsableFCHADomains)
< maxPaths) {
_logger.info("NumPathsMatcher disqualified pool: " + pool.getNativeGuid()
+ " max_paths: " + maxPaths
+ " because insufficient FC ports");
continue;
}
// If we need two or more paths, must have at least two HA Domains
if (!system.getIsDriverManaged()
&& !system.getSystemType().equals(DiscoveredSystemObject.Type.scaleio.name())
&& !system.getSystemType().equals(DiscoveredSystemObject.Type.xtremio.name())
&& !system.getSystemType().equals(DiscoveredSystemObject.Type.ceph.name())) {
if (maxPaths >= 2 && cachedUsableFCHADomains.get(dev) < 2) {
_logger.info("NumPathsMatcher disqualified pool: " + pool.getNativeGuid() + " max_paths: " + maxPaths
+ " because insufficient FC cpus (StorageHADomains)");
continue;
}
}
}
if (checkIP) {
if (numberOfUsablePorts(dev, Transport.IP, vArrays, cachedUsableIPPorts, cachedUsableIPHADomains)
< maxPaths) {
_logger.info("NumPathsMatcher disqualified pool: " + pool.getNativeGuid()
+ " max_paths: " + maxPaths
+ " because insufficient IP ports");
continue;
}
StorageDriverManager storageDriverManager = (StorageDriverManager) StorageDriverManager.getApplicationContext().getBean(
StorageDriverManager.STORAGE_DRIVER_MANAGER);
// If we need two or more paths, must have at least two HA Domains
if (!storageDriverManager.isDriverManaged(system.getSystemType())
&& !system.getSystemType().equals(DiscoveredSystemObject.Type.scaleio.name())
&& !system.getSystemType().equals(DiscoveredSystemObject.Type.xtremio.name())
&& !system.getSystemType().equals(DiscoveredSystemObject.Type.ceph.name())) {
if (maxPaths >= 2 && cachedUsableIPHADomains.get(dev) < 2) {
_logger.info("NumPathsMatcher disqualified pool: " + pool.getNativeGuid() + " max_paths: " + maxPaths
+ " because insufficient IP cpus (StorageHADomains)");
continue;
}
}
}
matchedPools.add(pool);
}
if (CollectionUtils.isEmpty(matchedPools)) {
errorMessage.append(String.format("No storage pool is matching with the VPool maximum path parameter %d. ", maxPaths));
_logger.error(errorMessage.toString());
}
_logger.info("NumPathsMatcher maxPaths: " + maxPaths + " passed " + matchedPools.size() + " pools");
return matchedPools;
}
/**
* Returns the number of usable storage ports.
* Also generates the number of distinct StorageHADomains in the cachedUsableHADomains map.
* To be "usable", a port must be:
* 1. Not inactive (or null)
* 2. Registered
* 3. Must be front-end port
* 4. Must be associated with a Network
* 5. OperationalStatus must not be NOT_OK
* 6. Port must not be over a ceiling value
*
* @param storageDeviceURI -- device URI
* @param transportType -- FC or IP
* @param vArrays
* @param cachedUsablePorts A cache of device URI to number of usable ports of specified transportType
* @param cachedUsableHADomains -- A cache of device URI to number of HA domains available
* @return number of usable ports
*/
private int numberOfUsablePorts(URI storageDeviceURI,
StorageProtocol.Transport transportType,
Set<String> vArrays, Map<URI, Integer> cachedUsablePorts,
Map<URI, Integer> cachedUsableHADomains) {
Integer usable = cachedUsablePorts.get(storageDeviceURI);
if (usable != null) {
return usable;
} else {
usable = 0;
}
StorageSystem storageDevice = _objectCache.queryObject(StorageSystem.class, storageDeviceURI);
if (storageDevice == null || storageDevice.getInactive() == true) {
cachedUsablePorts.put(storageDeviceURI, new Integer(0));
cachedUsableHADomains.put(storageDeviceURI, new Integer(0));
return 0;
}
Set<URI> haDomains = new HashSet<URI>();
URIQueryResultList storagePortURIs = new URIQueryResultList();
_objectCache.getDbClient().queryByConstraint(
ContainmentConstraint.Factory.getStorageDeviceStoragePortConstraint(
storageDeviceURI),
storagePortURIs);
List<StoragePort> storagePorts = _objectCache.queryObject(StoragePort.class,
storagePortURIs);
// CTRL-10769. If ports part of selected vArrays are of RDF type only, skip that system's pools.
boolean nonFrontendPortFound = false;
boolean atLeastOneFrontEndPortFound = false;
for (StoragePort storagePort : storagePorts) {
if (transportType.name().equals(storagePort.getTransportType())
&& vArrays != null && storagePort.getTaggedVirtualArrays() != null
&& !Sets.intersection(vArrays, storagePort.getTaggedVirtualArrays()).isEmpty()) {
if (!StoragePort.PortType.frontend.name().equals(storagePort.getPortType())) {
nonFrontendPortFound = true;
} else {
atLeastOneFrontEndPortFound = true;
break;
}
}
}
if (nonFrontendPortFound && !atLeastOneFrontEndPortFound) {
cachedUsablePorts.put(storageDeviceURI, new Integer(0));
cachedUsableHADomains.put(storageDeviceURI, new Integer(0));
return 0;
}
for (StoragePort storagePort : storagePorts) {
// must not be null or incompatible or inactive
_logger.debug("Checking port: " + storagePort.getNativeGuid());
if (transportType.name().equals(storagePort.getTransportType()) &&
_portMetricsProcessor.isPortUsable(storagePort, vArrays) &&
!_portMetricsProcessor.isPortOverCeiling(storagePort, storageDevice, false)) {
haDomains.add(storagePort.getStorageHADomain());
usable++;
}
}
_logger.info("System: " + storageDevice.getNativeGuid() + " transport: " + transportType
+ " usable: " + usable + " haDomains" + haDomains.size());
cachedUsablePorts.put(storageDeviceURI, usable);
cachedUsableHADomains.put(storageDeviceURI, haDomains.size());
return usable;
}
}