/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.Network;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StoragePort.TransportType;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.VirtualNAS;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.pools.VirtualArrayAssignmentChanges;
import com.emc.storageos.model.pools.VirtualArrayAssignments;
import com.emc.storageos.networkcontroller.impl.NetworkAssociationHelper;
import com.emc.storageos.util.ConnectivityUtil;
import com.emc.storageos.util.NetworkLite;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.volumecontroller.AttributeMatcher;
import com.emc.storageos.volumecontroller.impl.utils.ImplicitPoolMatcher;
/**
* A helper class to update Network/Storage Port as well as the VirtualArray/Storage Pool
* associations when a storage port is discovered. There are two possible updates:
* <ul>
*
* <li>When a storage port is discovered, if its end point is found to already belong to a varray, the storage port is associated with the
* network. The reverse is true when a storage port is removed however, this use case is not yet supported.</li>
*
* <li>When the storage port is added or registered is associated with a network, and the network is associated with a varray, if the
* network is the first in the varray that has storage ports from a new array, the pools of the array will be implicitly associated with the
* varray. The reverse is true when a storage port is removed or de-registered.</li>
* </ul>
*
*/
public class StoragePortAssociationHelper {
private static final Logger _log = LoggerFactory.getLogger(StoragePortAssociationHelper.class);
/**
* TODO Need to be removed after VNXFile changes
*
* Updates the Network/Storage Port as well as the Varray/Storage Pool associations
* when ports are added.
* This code assumes that an endpoint exists in one and only one network
* returns true if any ports are associated with network.
*
* @param ports the list of added ports
* @param dbClient an instance of {@link DbClient} @ return boolean
* @throws IOException when a database error occurs
*/
public static void updatePortAssociations(Collection<StoragePort> ports, DbClient dbClient) throws IOException {
// Find the networks that have the storage ports' end points.
Map<NetworkLite, List<StoragePort>> networkPorts = getNetworksMap(ports, dbClient);
if (networkPorts.isEmpty()) {
_log.info("The storage ports are not in any network.");
return; // nothing to do
}
for (Map.Entry<NetworkLite, List<StoragePort>> portsForNetwork : networkPorts.entrySet()) {
NetworkAssociationHelper.updatePortAssociations(portsForNetwork.getKey(),
portsForNetwork.getValue(), dbClient);
}
StoragePoolAssociationHelper.updateVArrayRelations(ports, null, dbClient, null);
}
/**
* This method bundles updation of
* 1. Port to Network
* 2. Pool to VArrays
*
* @param ports
* @param networkPorts
* @param dbClient
* @throws IOException
*/
private static void updatePortAssociations(Collection<StoragePort> ports, Map<NetworkLite, List<StoragePort>> networkPorts,
DbClient dbClient) throws IOException {
updatePortToNetworkAssociation(networkPorts, dbClient);
StoragePoolAssociationHelper.updateVArrayRelations(ports, null, dbClient, null);
}
/**
* This method updates 1. Port to Network 2. Port to VArrays
*
* @param networkPorts
* @param dbClient
*/
private static void updatePortToNetworkAssociation(Map<NetworkLite, List<StoragePort>> networkPorts, DbClient dbClient) {
for (Map.Entry<NetworkLite, List<StoragePort>> portsForNetwork : networkPorts
.entrySet()) {
NetworkAssociationHelper.updatePortAssociations(portsForNetwork.getKey(),
portsForNetwork.getValue(), dbClient);
}
}
/**
* Group Ports by Network
*
* @param ports
* @param dbClient
* @return
*/
private static Map<NetworkLite, List<StoragePort>> groupPortsByNetwork(
Collection<StoragePort> ports, DbClient dbClient) {
return getNetworksMap(ports, dbClient);
}
/**
* get Ids
*
* @param pools
* @return
*/
private static Set<URI> getStoragePoolIds(List<StoragePool> pools) {
Set<URI> poolUris = new HashSet<URI>();
for (StoragePool pool : pools) {
poolUris.add(pool.getId());
}
return poolUris;
}
/**
* This method executes process to update varrays for the passed port parameter.
*
* @param port storage port for which varrays have been changed
* @param dbClient
* @param coordinator
* @param pools pools which may require vpool changes as a result of port varray changes
* @param varrayAssignmentChanges varray changes for the port
*/
public static void runUpdatePortAssociationsProcessForVArrayChange(StoragePort port,
DbClient dbClient, CoordinatorClient coordinator, List<StoragePool> pools,
VirtualArrayAssignmentChanges varrayAssignmentChanges) {
try {
// If there was no change in connection between virtual array and storage system which owns port,
// we will run only NumPathsMatcher. Even when varray to storage system connectivity had not changed,
// change in varray ports may have impact on max. number of paths available for varray pools.
// If there was change in connectivity between virtual array and storage system, we will run all vpool
// matchers.
// This is our storage system.
URI storageSystemURI = port.getStorageDevice();
Set<String> varraysToAddIds = new HashSet<>();
Set<String> varraysToRemoveIds = new HashSet<>();
VirtualArrayAssignments varraysToAdd = varrayAssignmentChanges.getAdd();
if (varraysToAdd != null) {
varraysToAddIds = varraysToAdd.getVarrays();
}
VirtualArrayAssignments varraysToRemove = varrayAssignmentChanges.getRemove();
if (varraysToRemove != null) {
varraysToRemoveIds = varraysToRemove.getVarrays();
}
Set<String> varraysWithOutChangedConnectivity = new HashSet<>(varraysToAddIds);
varraysWithOutChangedConnectivity.addAll(varraysToRemoveIds);
// Set of varrays which changed connection to our storage system as result of storage port to varrays assignment change.
Set<String> varraysWithChangedConnectivity = StoragePortAssociationHelper.getVArraysWithChangedConnectivityToStorageSystem(port,
varraysToAddIds,
varraysToRemoveIds, dbClient);
varraysWithOutChangedConnectivity.removeAll(varraysWithChangedConnectivity);
Collection<StoragePort> ports = Collections.singleton(port);
if (null == pools) {
pools = new ArrayList<StoragePool>();
}
// for better reading, added a method to group Ports by Network
Map<NetworkLite, List<StoragePort>> portsByNetwork = groupPortsByNetwork(ports, dbClient);
if (!portsByNetwork.isEmpty()) {
updatePortAssociations(ports, portsByNetwork, dbClient);
// if any ports are associated with network, then add pools to existing list and run matching pools
Set<URI> poolUris = getStoragePoolIds(pools);
List<StoragePool> modifiedPools = StoragePoolAssociationHelper.getStoragePoolsFromPorts(dbClient, ports, null);
for (StoragePool pool : modifiedPools) {
if (!poolUris.contains(pool.getId())) {
pools.add(pool);
}
}
}
if (!varraysWithChangedConnectivity.isEmpty()) {
// If there are varrays which changed connectivity to our storage system, we need to process all their vpools to match them
// to
// our system's storage pools.
// Match the VPools defined in VArrays with changed connectivity to the StoragePools with all vpool matchers
_log.info("Varrays which changed connectivity to storage system {} are {}", storageSystemURI,
varraysWithChangedConnectivity);
List<URI> vpoolURIs = getVpoolsForVarrays(varraysWithChangedConnectivity, dbClient);
_log.info("There are {} vpools for varrays. Execute all vpool matchers for vpools {}", vpoolURIs.size(), vpoolURIs);
StringBuffer errorMessage = new StringBuffer();
ImplicitPoolMatcher.matchModifiedStoragePoolsWithVirtualPools(pools, vpoolURIs, dbClient, coordinator,
AttributeMatcher.VPOOL_MATCHERS, errorMessage);
}
if (!varraysWithOutChangedConnectivity.isEmpty()) {
_log.info("Varrays which did not change connection to storage system {} are {}", storageSystemURI,
varraysWithOutChangedConnectivity);
// Match the VPools defined in VArrays without changed connectivity to the StoragePools only with num paths matcher
List<URI> vpoolURIs = getVpoolsForVarrays(varraysWithOutChangedConnectivity, dbClient);
_log.info("There are {} vpools for varrays. Execute num paths matcher for vpools {}", vpoolURIs.size(), vpoolURIs);
StringBuffer errorMessage = new StringBuffer();
ImplicitPoolMatcher.matchModifiedStoragePoolsWithVirtualPools(pools, vpoolURIs, dbClient, coordinator,
AttributeMatcher.CONNECTIVITY_PLACEMENT_MATCHERS, errorMessage);
}
// get all the system that were affected and update their virtual
// arrays
HashSet<URI> systemsToProcess = StoragePoolAssociationHelper.getStorageSytemsFromPorts(ports, null);
// Now that pools have changed varrays, we need to update RP systems
ConnectivityUtil.updateRpSystemsConnectivity(systemsToProcess, dbClient);
} catch (Exception e) {
_log.error("Update Port Association process failed", e);
}
}
/**
* This method is responsible for
* 1. Update pools to virtual arrays & system to virtual arrays in vplex case
* 2. Run implicit Pool Matcher
* 3. Run RP Connectivity Process
*
* @param ports
* @param remPorts
* @param dbClient
* @param coordinator
* @throws IOException
*/
public static void runUpdatePortAssociationsProcess(Collection<StoragePort> ports, Collection<StoragePort> remPorts,
DbClient dbClient, CoordinatorClient coordinator, List<StoragePool> pools) {
try {
if (null == pools) {
pools = new ArrayList<StoragePool>();
}
if (null == ports) {
ports = new ArrayList<StoragePort>();
}
if (null != remPorts) {
ports.addAll(remPorts);
}
// for better reading, added a method to group Ports by Network
Map<NetworkLite, List<StoragePort>> portsByNetwork = groupPortsByNetwork(ports, dbClient);
if (!portsByNetwork.isEmpty()) {
updatePortAssociations(ports, portsByNetwork, dbClient);
// if any ports are associated with network, then add pools to existing list and run matching pools
Set<URI> poolUris = getStoragePoolIds(pools);
List<StoragePool> modifiedPools = StoragePoolAssociationHelper.getStoragePoolsFromPorts(dbClient, ports, remPorts);
for (StoragePool pool : modifiedPools) {
if (!poolUris.contains(pool.getId())) {
pools.add(pool);
}
}
}
StringBuffer errorMessage = new StringBuffer();
// Match the VPools to the StoragePools
ImplicitPoolMatcher.matchModifiedStoragePoolsWithAllVirtualPool(pools, dbClient, coordinator, errorMessage);
// get all the system that were affected and update their virtual
// arrays
HashSet<URI> systemsToProcess = StoragePoolAssociationHelper.getStorageSytemsFromPorts(ports, remPorts);
// Now that pools have changed varrays, we need to update RP systems
ConnectivityUtil.updateRpSystemsConnectivity(systemsToProcess, dbClient);
} catch (Exception e) {
_log.error("Update Port Association process failed", e);
}
}
/**
* This method finds varrays which change connection (either are newly connected or lost old connection) to storage
* system of the passed port parameter.
*
* @param port storage port for which varrays were added/removed
* @param varraysToAddIds set of added varrays to port
* @param varraysToRemoveIds set of removed varrays from port
* @param dbClient
* @return set of varrays which changed connection to port's storage system
*/
private static Set<String> getVArraysWithChangedConnectivityToStorageSystem(StoragePort port, Set<String> varraysToAddIds,
Set<String> varraysToRemoveIds, DbClient dbClient) {
// This is our storage system.
URI storageSystemURI = port.getStorageDevice();
_log.info("Port {} belongs to storage system {}", port.getId(), storageSystemURI);
// Set of varrays which changed connection to the storage system.
Set<String> varraysWithChangedConnectivity = new HashSet<>();
// Check which varrays in "ToAdd" set already have at least one other port from our storage system.
// For these varrays there is no connection change to the storage system.
if (varraysToAddIds != null && !varraysToAddIds.isEmpty()) {
_log.info("New varrays {} added to the port {}", varraysToAddIds, port.getId());
Set<String> varraysToAddWithChangedConnectivity = new HashSet<>(varraysToAddIds);
for (String varrayId : varraysToAddIds) {
URIQueryResultList storagePortURIs = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getVirtualArrayStoragePortsConstraint(varrayId),
storagePortURIs);
for (URI portURI : storagePortURIs) {
// If this is the same port as was added to varray in the change request, skip and continue
if (port.getId().equals(portURI)) {
continue;
}
StoragePort varrayPort = (StoragePort) dbClient.queryObject(portURI);
if (!NullColumnValueGetter.isNullURI(varrayPort.getStorageDevice()) &&
storageSystemURI.equals(varrayPort.getStorageDevice())) {
// This varray port belongs to our storage system
// At least one other port from our storage system belongs to varray.
// Addition of a new port to this varray does not change connection between our storage
// system and varray.
// We can stop here processing varray ports.
varraysToAddWithChangedConnectivity.remove(varrayId);
_log.info("Varray {} already has connection to storage system {} through port {}", varrayId, storageSystemURI,
portURI);
break;
}
}
}
_log.info("New varrays connected to storage system {} are {}", storageSystemURI, varraysToAddWithChangedConnectivity);
varraysWithChangedConnectivity.addAll(varraysToAddWithChangedConnectivity);
}
// Check which varrays "ToRemove" have at least one other port from our storage system.
// For these varrays there is no connection change to our storage system when port in the request is removed from
// the varray.
if (varraysToRemoveIds != null && !varraysToRemoveIds.isEmpty()) {
_log.info("Varrays {} are removed from the port {}", varraysToRemoveIds, port.getId());
Set<String> varraysToRemoveWithChangedConnectivity = new HashSet<>(varraysToRemoveIds);
for (String varrayId : varraysToRemoveIds) {
URIQueryResultList storagePortURIs = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getVirtualArrayStoragePortsConstraint(varrayId),
storagePortURIs);
for (URI portURI : storagePortURIs) {
StoragePort varrayPort = (StoragePort) dbClient.queryObject(portURI);
if (!NullColumnValueGetter.isNullURI(varrayPort.getStorageDevice()) &&
storageSystemURI.equals(varrayPort.getStorageDevice())) {
// This varray port belongs to our storage system
_log.info("Varray {} has connection to storage system {} through port {}", varrayId, storageSystemURI, portURI);
// There is other port from our storage system which belongs to varray.
// We can stop here processing varray ports.
varraysToRemoveWithChangedConnectivity.remove(varrayId);
break;
}
}
}
_log.info("Varrays which lost connection to storage system {} are {}", storageSystemURI,
varraysToRemoveWithChangedConnectivity);
varraysWithChangedConnectivity.addAll(varraysToRemoveWithChangedConnectivity);
}
_log.info("Varrays with changed connection to storage system {} are {}", storageSystemURI, varraysWithChangedConnectivity);
return varraysWithChangedConnectivity;
}
/**
* This method return list of VirtualPoll URIs defined in the past set of VirtualArray(s)
*
* @param varrayIDs set of virtual arrays URIs
* @param dbClient
*
* @return list of virtual pool URIs
*/
public static List<URI> getVpoolsForVarrays(Set<String> varrayIDs, DbClient dbClient) {
List<URI> vpoolURIs = new ArrayList<>();
for (String varrayID : varrayIDs) {
URI varrayURI = URI.create(varrayID);
URIQueryResultList resultList = new URIQueryResultList();
dbClient.queryByConstraint(
ContainmentConstraint.Factory.getVirtualArrayVirtualPoolConstraint(varrayURI),
resultList);
Iterator<URI> iterator = resultList.iterator();
while (iterator.hasNext()) {
URI vpoolId = iterator.next();
if (vpoolURIs.contains(vpoolId)) { // already added, ignore
continue;
}
vpoolURIs.add(vpoolId);
}
}
return vpoolURIs;
}
/**
* it return VirtualNAS from database using NativeId
*
* @param nativeId
* @param dbClient
* @return VirtualNAS based on nativeId
*/
private static VirtualNAS findvNasByNativeId(String nativeId, DbClient dbClient) {
URIQueryResultList results = new URIQueryResultList();
VirtualNAS vNas = null;
dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getVirtualNASByNativeGuidConstraint(nativeId),
results);
Iterator<URI> iter = results.iterator();
while (iter.hasNext()) {
VirtualNAS tmpVnas = dbClient.queryObject(VirtualNAS.class, iter.next());
if (tmpVnas != null && !tmpVnas.getInactive()) {
vNas = tmpVnas;
_log.info("found virtual NAS {}", tmpVnas.getNativeGuid() + ":" + tmpVnas.getNasName());
break;
}
}
return vNas;
}
/**
* This method is responsible for
* Assign the virtual arrays of storage port to virtual nas
*
* @param ports
* @param remPorts
* @param dbClient
* @param coordinator
* @throws IOException
*/
public static void runUpdateVirtualNasAssociationsProcess(Collection<StoragePort> ports, Collection<StoragePort> remPorts,
DbClient dbClient) {
try {
List<VirtualNAS> modifiedServers = new ArrayList<VirtualNAS>();
if (ports != null && !ports.isEmpty()) {
// for better reading, added a method to group Ports by Network
Map<String, List<NetworkLite>> vNasNetworkMap = getVNasNetworksMap(ports, dbClient);
if (!vNasNetworkMap.isEmpty()) {
for (Map.Entry<String, List<NetworkLite>> vNasEntry : vNasNetworkMap.entrySet()) {
String nativeId = vNasEntry.getKey();
VirtualNAS vNas = findvNasByNativeId(nativeId, dbClient);
if (vNas != null) {
for (NetworkLite network : vNasEntry.getValue()) {
Set<String> varraySet = new HashSet<String>(network.getAssignedVirtualArrays());
if (vNas.getAssignedVirtualArrays() == null) {
vNas.setAssignedVirtualArrays(new StringSet());
}
vNas.getAssignedVirtualArrays().addAll(varraySet);
_log.info("found virtual NAS: {} and varrays: {}", vNas.getNasName(), varraySet.toString());
}
modifiedServers.add(vNas);
}
}
}
}
if (!modifiedServers.isEmpty()) {
dbClient.persistObject(modifiedServers);
}
} catch (Exception e) {
_log.error("Update Port Association process failed", e);
}
}
/**
* This method is responsible for
* Assign the virtual arrays of storage port to virtual nas
*
* @param ports
* @param remPorts
* @param dbClient
* @param coordinator
* @throws IOException
*/
public static void runUpdateVirtualNasAssociationsProcess(Network net, Collection<StoragePort> ports, Collection<StoragePort> remPorts,
DbClient dbClient) {
if (net != null && ports.isEmpty()) {
// In Some scenario while updating network we update only VARRAY not ports,
// so in that case ports to add/remove will be empty..
ports = NetworkAssociationHelper.getNetworkStoragePorts(net.getId().toString(), null, dbClient);
}
runUpdateVirtualNasAssociationsProcess(ports, remPorts, dbClient);
}
/**
* Gets the networks of the storage ports organized in a map.
* This code assumes that an end point exists in one and only one network.
*
* @param sports the ports
* @param dbClient an instance of {@link DbClient}
* @return a map of networks and the storage ports that are associated to them.
*/
private static Map<NetworkLite, List<StoragePort>> getNetworksMap(
Collection<StoragePort> sports, DbClient dbClient) {
Map<NetworkLite, List<StoragePort>> networkPorts = new HashMap<NetworkLite, List<StoragePort>>();
NetworkLite network;
List<StoragePort> list = null;
for (StoragePort sport : sports) {
network = NetworkUtil.getEndpointNetworkLite(sport.getPortNetworkId(), dbClient);
if (network != null && network.getInactive() == false
&& network.getTransportType().equals(sport.getTransportType())) {
list = networkPorts.get(network);
if (list == null) {
list = new ArrayList<StoragePort>();
networkPorts.put(network, list);
}
list.add(sport);
}
}
return networkPorts;
}
/**
* Returns VirtualNAS associated with the given storage port
*
* @param sp StorgaePort
* @param dbClient
* @return VirtualNAS associated with StorgaePort
*/
public static List<VirtualNAS> getStoragePortVirtualNAS(StoragePort sp, DbClient dbClient) {
List<VirtualNAS> virtualNASList = new ArrayList<VirtualNAS>();
URIQueryResultList vNasUriList = new URIQueryResultList();
dbClient.queryByConstraint(
ContainmentConstraint.Factory.getVirtualNASContainStoragePortConstraint(sp.getId()), vNasUriList);
Iterator<URI> vNasIter = vNasUriList.iterator();
while (vNasIter.hasNext()) {
VirtualNAS vNas = dbClient.queryObject(VirtualNAS.class, vNasIter.next());
if (vNas != null && !vNas.getInactive()) {
virtualNASList.add(vNas);
_log.info("Found virtual NAS: {} for storageport: {}", vNas.getNasName(), sp.getLabel());
}
}
return virtualNASList;
}
/**
* Gets the networks of the virtual nas and organized in a map.
* This code assumes that an end point exists in one and only one network.
*
* @param sports the ports
* @param dbClient an instance of {@link DbClient}
* @return a map of networks and the virtual nas that are associated to them.
*/
private static Map<String, List<NetworkLite>> getVNasNetworksMap(
Collection<StoragePort> sports, DbClient dbClient) {
Map<String, List<NetworkLite>> vNasNetwork = new HashMap<>();
NetworkLite network;
List<VirtualNAS> vNasList = null;
List<NetworkLite> list = null;
for (StoragePort sport : sports) {
if (TransportType.IP.name().equalsIgnoreCase(sport.getTransportType())) {
StorageSystem system = dbClient.queryObject(StorageSystem.class, sport.getStorageDevice());
if (DiscoveredDataObject.Type.vnxfile.name().equals(system.getSystemType())
|| DiscoveredDataObject.Type.isilon.name().equals(system.getSystemType())
|| DiscoveredDataObject.Type.unity.name().equals(system.getSystemType())) {
network = NetworkUtil.getEndpointNetworkLite(sport.getPortNetworkId(), dbClient);
vNasList = getStoragePortVirtualNAS(sport, dbClient);
if (network != null && network.getInactive() == false
&& network.getTransportType().equals(sport.getTransportType())
&& vNasList != null && !vNasList.isEmpty()) {
for (VirtualNAS vNas : vNasList) {
list = vNasNetwork.get(vNas.getNativeGuid());
if (list == null) {
list = new ArrayList<NetworkLite>();
vNasNetwork.put(vNas.getNativeGuid(), list);
}
list.add(network);
}
}
}
}
}
return vNasNetwork;
}
}