/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.networkcontroller.impl;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
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.URIQueryResultList;
import com.emc.storageos.db.client.model.Network;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.StringSetUtil;
import com.emc.storageos.util.NetworkLite;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.volumecontroller.impl.StoragePoolAssociationHelper;
import com.emc.storageos.volumecontroller.impl.StoragePortAssociationHelper;
/**
* A helper class to manage/update the network associations in the following cases
* <ul>
*
* <li>The network is added to a varray. In this case, 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 network is removed from a varray.</li>
*
* <li>New end points are added to the network and in this case the network will be associated with the storage ports the end points
* correspond to when the storage ports that are already in the database. The reverse is true when an end point is removed from a network.
* <em>Note</em> that this may also result in a change in the network's varray/storage pools associations described in the previous
* paragraph.</li>
* </ul>
*
*/
public class NetworkAssociationHelper {
private static final Logger _log = LoggerFactory.getLogger(NetworkAssociationHelper.class);
/**
* When this call is made, the update already took place. Further the list of endpoints and
* varrays are filtered to what was effectively added and not what was requested by the user.
*
* @param network the network that was changed.
* @param addVarrays the varrays added to the network
* @param remVarray the varrays removed from the network
* @param addEps the added endpoints
* @param remEps the removed endpoints
*/
public static void handleNetworkUpdated(Network network, Collection<URI> addVarrays, Collection<URI> remVarray,
Collection<String> addEps, Collection<String> remEps, DbClient dbClient, CoordinatorClient coordinator) {
// First update removed endpoints
List<StoragePort> remPorts = getEndPointsStoragePorts(remEps, dbClient);
if (!remPorts.isEmpty()) {
// clear all network and varrays associations
clearPortAssociations(remPorts, dbClient);
}
List<StoragePort> addPorts = getEndPointsStoragePorts(addEps, dbClient);
if (!addPorts.isEmpty()) {
// update the ports implicitly connected varrays
updatePortAssociations(network, addPorts, dbClient);
}
List<StoragePort> createdAndUpdatedPorts = new ArrayList<StoragePort>(addPorts);
if ((addVarrays != null && !addVarrays.isEmpty()) || (remVarray != null && !remVarray.isEmpty())) {
// varray changed, update existing ports in the network
List<StoragePort> updatedPorts = getNetworkStoragePorts(network.getId().toString(), addEps, dbClient);
createdAndUpdatedPorts.addAll(updatedPorts);
updatePortAssociations(network, updatedPorts, dbClient);
}
if (!remPorts.isEmpty() ||
(addVarrays != null && !addVarrays.isEmpty()) || (remVarray != null && !remVarray.isEmpty())) {
// when ports are removed or varrays changed, a full recompute of connected networks is needed
setNetworkConnectedVirtualArrays(network, true, dbClient);
} else if (!addPorts.isEmpty()) {
// update the network implicitly connected varrays based on added ports
updateConnectedVirtualArrays(network, addPorts, true, dbClient);
}
// now I need to handle pools
StoragePoolAssociationHelper.handleNetworkUpdated(network, addVarrays, remVarray, createdAndUpdatedPorts, remPorts, dbClient,
coordinator);
// Update the virtual nas with network changes!!!
StoragePortAssociationHelper.runUpdateVirtualNasAssociationsProcess(network, addPorts, remPorts, dbClient);
}
/**
* Updates the implicitly connected virtual arrays for the passed
* network, if necessary, based on the passed storage ports. This
* update is needed when ports' varrays are changed or when ports
* are added or removed from a network. If the affected network
* is routed to other networks, this function will also check if
* any updates are needed for the routed networks.
*
*
* @param network A reference to the network.
* @param storagePorts A list of the storage ports being added or removed
* or the list of storage ports that have had their virtual arrays
* changed.
* @param isAdd true if the storage ports are being added or if their
* virtual arrays were added. In this case the new virtual arrays
* will be added to the network. This flag is false when ports
* are removed or when their virtual arrays are removed. In this
* case a new list of connected virtual arrays is computed and the
* a full set is performed on the network and its routed networks.
* This option clearly costs more.
* @param dbClient A reference to the DB client.
*/
public static void updateConnectedVirtualArrays(Network network,
List<StoragePort> storagePorts, boolean isAdd, DbClient dbClient) {
_log.info("Updating connected virtual arrays for network {}",
network.getLabel());
// Check whether ports are added/removed to/from the network.
if (isAdd) {
_log.info("Storage ports were added to network or have virtual arrays added.");
Set<String> varraysToAdd = new HashSet<String>();
StringSet networksConnectedVArrays = network.getConnectedVirtualArrays();
_log.info("Current connected virtual arrays are {} ", networksConnectedVArrays);
for (StoragePort storagePort : storagePorts) {
_log.debug("Processing virtual arrays for storage port {}", storagePort.getNativeGuid());
StringSet storagePortTaggedVArrays = storagePort.getTaggedVirtualArrays();
if ((storagePortTaggedVArrays != null) && (!storagePortTaggedVArrays.isEmpty())) {
for (String storagePortTaggedVArray : storagePortTaggedVArrays) {
_log.debug("Storage port assigned to virtual array {}", storagePortTaggedVArray);
// If the storage port is being added to the network,
// then network is implicitly connected to the port's
// assigned virtual arrays, so add them if necessary.
if ((networksConnectedVArrays == null)
|| (!networksConnectedVArrays.contains(storagePortTaggedVArray))) {
varraysToAdd.add(storagePortTaggedVArray);
}
}
}
}
if (!varraysToAdd.isEmpty()) {
addNetworkConnectedVarrays(network, varraysToAdd, true, dbClient);
}
} else {
_log.info("Storage ports {} removed from network or have had virtual arrays removed");
setNetworkConnectedVirtualArrays(network, true, dbClient);
}
}
public static void setNetworkConnectedVirtualArrays(Network network, boolean cascade, DbClient dbClient) {
// compute the new virtual arrays
StringSet newSet = getNetworkConnectedVirtualArrays(network.getId(), network.getRoutedNetworks(),
network.getAssignedVirtualArrays(), dbClient);
// check the new list of virtual arrays is different from the old
boolean changed = StringSetUtil.isChanged(network.getConnectedVirtualArrays(), newSet);
if (changed) {
_log.info("Updating connected virtual arrays for network {}. New virtual arrays {}",
network.getId(), newSet);
network.replaceConnectedVirtualArrays(newSet);
dbClient.updateAndReindexObject(network);
if (cascade) {
// now update the routed networks
List<Network> routednetworks = getNetworkRoutedNetworksForUpdate(network, dbClient);
for (Network routedNetwork : routednetworks) {
NetworkLite lite = NetworkUtil.getNetworkLite(routedNetwork.getId(), dbClient);
newSet = getNetworkConnectedVirtualArrays(routedNetwork.getId(), lite.getRoutedNetworks(),
lite.getAssignedVirtualArrays(), dbClient);
_log.info("Updating connected virtual arrays for routed network {}. New virtual arrays {}",
network.getId(), newSet);
routedNetwork.replaceConnectedVirtualArrays(newSet);
dbClient.updateAndReindexObject(routedNetwork);
}
}
} else {
_log.info("The new virtual arrays {} are the same as the existing ones. " +
"No update is needed for network {}",
newSet, network.getId());
}
}
/**
* Of the passed list of virtual arrays, determines those for which an
* active storage port in the passed network has been assigned. The
* function ignores the passed ports, which may be in the process of
* being removed from the virtual arrays or the network.
*
* @param network A reference to the network.
* @param varrayIds The id of the virtual arrays.
* @param ignorePorts Storage ports to be ignored, or null.
* @param dbClient A reference to a DB client.
*
* @return The ids of the varrays for which a storage port in the passed network
* is assigned.
*/
public static Set<String> getVArraysNotAssignedToStoragePortInNetwork(
Network network, Set<String> varrayIds, List<URI> ignorePorts, DbClient dbClient) {
_log.info("Getting varrays not assigned to any storage ports in network {}", network.getId());
Set<String> varraysWithoutAssignedStoragePort = new HashSet<String>();
// Get the URIs of the active storage ports in the passed network.
List<URI> activeNetworkStoragePortURIs = new ArrayList<URI>();
List<StoragePort> activeNetworkStoragePorts = CustomQueryUtility
.queryActiveResourcesByAltId(dbClient, StoragePort.class, "network", network
.getId().toString());
for (StoragePort networkStoragePort : activeNetworkStoragePorts) {
activeNetworkStoragePortURIs.add(networkStoragePort.getId());
}
// Cycle over the passed virtual arrays to determine which are not
// assigned to a storage port in the passed network.
for (String varrayId : varrayIds) {
boolean storagePortAssignedToVArray = false;
// Get the storage ports assigned to the virtual array.
URIQueryResultList queryResults = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getAssignedVirtualArrayStoragePortsConstraint(varrayId), queryResults);
Iterator<URI> resultsIter = queryResults.iterator();
while (resultsIter.hasNext()) {
URI storagePortURI = resultsIter.next();
_log.info("Checking virtual array storage port {}", storagePortURI);
// Ignore the passed port if not null. This
// could be a port we are in the process of
// removing from the virtual array.
if ((ignorePorts != null) && (ignorePorts.contains(storagePortURI))) {
_log.info("Ignoring port {}", storagePortURI);
continue;
}
// Check if the assigned virtual array storage
// port is a storage port in the passed network.
// If so, set the flag and break.
if (activeNetworkStoragePortURIs.contains(storagePortURI)) {
storagePortAssignedToVArray = true;
break;
}
}
if (!storagePortAssignedToVArray) {
_log.info("Virtual array {} does not have an assigned storage port", varrayId);
varraysWithoutAssignedStoragePort.add(varrayId);
}
}
return varraysWithoutAssignedStoragePort;
}
/**
* For a list of storage ports in a network, update the ports' implicit (connected) varray associations.
*
* @param network the network the storage ports are in
* @param ports the list of storage ports requiring update
* @param dbClient an instance of the DB client
*/
public static void updatePortAssociations(Network network, List<StoragePort> ports, DbClient dbClient) {
Set<String> varraySet = network.getAssignedVirtualArrays() == null ? null : new HashSet<String>(network.getAssignedVirtualArrays());
for (StoragePort port : ports) {
if (!network.getId().equals(port.getNetwork())) {
port.setNetwork(network.getId());
}
port.replaceConnectedVirtualArray(varraySet);
_log.info("Setting the connected virtual arrays for added port {} to {}", port.getPortNetworkId(), varraySet);
}
dbClient.updateAndReindexObject(ports);
}
/**
* For a list of storage ports in a network, update the ports' implicit (connected) varray associations.
*
* @param network the network the storage ports are in
* @param ports the list of storage ports requiring update
* @param dbClient an instance of the DB client
*/
public static void updatePortAssociations(NetworkLite network, List<StoragePort> ports, DbClient dbClient) {
Set<String> varraySet = new HashSet<String>(network.getAssignedVirtualArrays());
for (StoragePort port : ports) {
port.setNetwork(network.getId());
port.replaceConnectedVirtualArray(varraySet);
_log.info("Setting the connected virtual arrays for added port {} to {}", port.getPortNetworkId(), varraySet);
}
dbClient.updateAndReindexObject(ports);
}
/**
* Clear the ports' implicit (connected) varray associations
*
* @param ports the list of storage ports requiring update
* @param dbClient an instance of the DB client
*/
public static void clearPortAssociations(List<StoragePort> ports, DbClient dbClient) {
for (StoragePort port : ports) {
port.setNetwork(NullColumnValueGetter.getNullURI());
port.clearConnectedVirtualArray();
_log.info("Cleared the connected virtual arrays for removed port {}", port.getPortNetworkId());
}
dbClient.updateAndReindexObject(ports);
}
/**
* A short cut to {@link #handleNetworkUpdated(Network, Collection, Collection, Collection, Collection, DbClient, CoordinatorClient)}
* that does not require all the parameters.
*
* @param network the network where the endpoints were added
* @param endpoints the endpoints that were added
* @param dbClient an instance of the DB client
* @param coordinator an instance of the coordinator service
*/
public static void handleEndpointsAdded(Network network,
Collection<String> endpoints, DbClient dbClient, CoordinatorClient coordinator) {
NetworkAssociationHelper.handleNetworkUpdated(network, null, null, endpoints, null, dbClient, coordinator);
}
/**
* Update the storage ports that correspond to the end points removed from the network to
* diassociate them from the network and clear their implicit (connected) varay associations.
*
* @param network the network where the endpoints are getting removed
* @param dbClient an instance of {@link DbClient}
*/
public static void handleEndpointsRemoved(Network network, Collection<String> endpoints,
DbClient dbClient, CoordinatorClient coordinator) {
// First update removed endpoints
List<StoragePort> remPorts = getEndPointsStoragePorts(endpoints, dbClient);
clearPortAssociations(remPorts, dbClient);
// now I need to handle pools
StoragePoolAssociationHelper.handleNetworkUpdated(network, null, null, null, remPorts, dbClient, coordinator);
}
/**
* Finds the storage ports that correspond to the end points. Not all end points would have a matching storage port.
*
* @param endpoints collection of endpoints
* @param dbClient an instance of {@link DbClient}
* @return a list containing the storage ports that could be matched to an endpoint in <code>endpoints</code>
*/
public static List<StoragePort> getEndPointsStoragePorts(Collection<String> endpoints,
DbClient dbClient) {
List<StoragePort> sports = new ArrayList<StoragePort>();
if (endpoints != null) {
for (String endpoint : endpoints) {
sports.addAll(getEndPointPorts(endpoint, dbClient));
}
}
return sports;
}
/**
* Remove the endpoints from their current networks and update the
* the port-to-transport-zone and pool-to-varray associations as needed.
*
* @param networkMap a map containing the current network for each endpoint
* @param network the network to which the endpoint are moving
*/
public static void handleRemoveFromOldNetworks(
Map<String, Network> networkMap, Network network, DbClient dbClient, CoordinatorClient coordinator) {
// rather then processing one end point at a time, try to bundle by network
List<Network> processedTzs = new ArrayList<Network>();
for (Network net : networkMap.values()) {
if (!net.getId().equals(network.getId()) && !processedTzs.contains(net)) {
// once we find a network that has not been handled, get all its endpoints in the map
List<String> eps = new ArrayList<String>();
for (String ep : networkMap.keySet()) {
if (networkMap.get(ep) == net) {
eps.add(ep);
}
}
// do the removal and update the associations
_log.info("Removing endpoints {} from network {} in order to add them to {}",
new Object[] { eps.toArray(), net.getLabel(), network.getLabel() });
net.removeEndpoints(eps);
handleNetworkUpdated(net, null, null, null, eps, dbClient, coordinator);
dbClient.updateAndReindexObject(net);
processedTzs.add(net);
}
}
}
/**
* Looks up the storage ports for a given end point. Returns empty list if a storage port could not be found.
*
* @param endPoint
* @param dbClient an instance of {@link DbClient}
* @return
*/
private static List<StoragePort> getEndPointPorts(String endPoint, DbClient dbClient) {
URIQueryResultList portUriList = new URIQueryResultList();
List<StoragePort> ports = new ArrayList<StoragePort>();
dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getStoragePortEndpointConstraint(endPoint), portUriList);
Iterator<URI> itr = portUriList.iterator();
while (itr.hasNext()) {
StoragePort port = dbClient.queryObject(StoragePort.class, itr.next());
if (port != null && !port.getInactive()) {
ports.add(port);
}
}
return ports;
}
/**
* Gets the networks of the storage ports organized in a map.
*
* @param endpoints the ports
* @param dbClient an instance of {@link DbClient}
* @return a map of networks and the storage ports that are associated to them.
*/
public static Map<String, Network> getNetworksMap(Collection<String> endpoints,
DbClient dbClient) {
Map<String, Network> networkEndPoints = new HashMap<String, Network>();
Network network;
// when a network is found, loop and add all endpoints to its list
// this collection is used to track what is not accounted for yet
List<String> remainingEndPoints = new ArrayList<String>(endpoints);
for (String endpoint : endpoints) {
// if the endpoint is not accounted for
if (remainingEndPoints.contains(endpoint)) {
// find its network
network = NetworkUtil.getEndpointNetwork(endpoint, dbClient);
if (network != null) {
for (String ep : endpoints) {
networkEndPoints.put(ep, network);
// remove from remainingEndPoints because it is accounted for
remainingEndPoints.remove(ep);
}
}
}
}
return networkEndPoints;
}
/**
* Returns all the storage ports in the network except those in the
* excludeEndpoints list
*
* @param networkUri the network id
* @param excludeEndpoints the port network ids that this function should
* filter out from the results/
* @param dbClient an instance of dbClient
* @return
*/
public static List<StoragePort> getNetworkStoragePorts(String networkUri,
Collection<String> excludeEndpoints, DbClient dbClient) {
_log.debug("Finding storage ports for network {} excluding {}",
networkUri, excludeEndpoints);
URIQueryResultList storagePortURIs = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getNetworkStoragePortConstraint(networkUri),
storagePortURIs);
Iterator<URI> storagePortURIsIter = storagePortURIs.iterator();
Iterator<StoragePort> portsItr = null;
portsItr = dbClient.queryIterativeObjects(
StoragePort.class, CustomQueryUtility.iteratorToList(storagePortURIsIter), true);
List<StoragePort> portsList = CustomQueryUtility.iteratorToList(portsItr);
if (excludeEndpoints != null && !excludeEndpoints.isEmpty()) {
// can't use portsItr to remove item (not supported). Get a new iterator
Iterator<StoragePort> itr = portsList.iterator();
while (itr.hasNext()) {
StoragePort port = itr.next();
for (String str : excludeEndpoints) {
if (port.getPortNetworkId().equals(str)) {
itr.remove();
break;
}
}
}
}
return portsList;
}
public static List<StoragePort> getNetworkConnectedStoragePorts(String networkUri, DbClient dbClient) {
List<StoragePort> ports = getNetworkStoragePorts(networkUri, null, dbClient);
NetworkLite lite = NetworkUtil.getNetworkLite(URI.create(networkUri), dbClient);
if (lite.getRoutedNetworks() != null) {
for (String str : lite.getRoutedNetworks()) {
ports.addAll(getNetworkStoragePorts(str, null, dbClient));
}
}
return ports;
}
/**
* Add a list of virtual arrays to a network connected virtual array. Optionally
* cascade this update to the network's routed virtual arrays
*
* @param network the network to update
* @param varraysToAdd the virtual arrays to be added
* @param cascade if true the update will also be made to any routed networks
* @param dbClient an instance of DbClient
*/
public static void addNetworkConnectedVarrays(Network network,
Set<String> varraysToAdd, boolean cascade, DbClient dbClient) {
// update the network
_log.info("Adding implicit connected virtual arrays {} for network {}",
network.getId(), varraysToAdd);
network.addConnectedVirtualArrays(varraysToAdd);
dbClient.updateAndReindexObject(network);
// if updating routed networks is also required
if (cascade) {
// get all the network's routed networks and make a list of all networks to update
List<Network> routedNetworks = getNetworkRoutedNetworksForUpdate(network,
dbClient);
// and update all of them
for (Network net : routedNetworks) {
_log.info("Adding implicit connected virtual arrays {} for routed network {}",
net.getId(), varraysToAdd);
net.addConnectedVirtualArrays(varraysToAdd);
}
dbClient.updateAndReindexObject(routedNetworks);
}
}
/**
* Computes the list of connected virtual arrays for a network based on the
* network's storage ports assigned virtual array, plus those of the network's
* routed networks
*
* @param networkUri the id of the network
* @param routedNetworks the ids of the routed network
* @param assignedVarrays
* @param dbClient and instance of DbClient
* @return
*/
public static StringSet getNetworkConnectedVirtualArrays(URI networkUri,
Set<String> routedNetworks, Set<String> assignedVarrays, DbClient dbClient) {
StringSet set = new StringSet();
if (assignedVarrays != null) {
set.addAll(assignedVarrays);
}
// TODO - One day when dbClient stops sending inactive objects, I can improve this and retrieve
// only the assignedVirtualArrays and reduce the memory footprint
List<StoragePort> allPorts = getNetworkStoragePorts(networkUri.toString(), null,
dbClient);
if (routedNetworks != null) {
for (String strUri : routedNetworks) {
allPorts.addAll(getNetworkStoragePorts(strUri, null, dbClient));
}
}
for (StoragePort port : allPorts) {
if (port != null && port.getInactive() == false && port.getTaggedVirtualArrays() != null) {
set.addAll(port.getTaggedVirtualArrays());
}
}
_log.debug("Found virtual arrays {} to be implicitly connected to network {}",
set, networkUri);
return set;
}
/**
* This function returns the routed networks for a given network. It however
* only retrieves one specific field for each. This is done to reduce the
* memory footprint and because NetworkLite cannot be used for updates.
*
* @param network the network for which the routed networks are requested
* @param dbClient an instance of DbClient
* @return the routed networks for the network.
*/
private static List<Network> getNetworkRoutedNetworksForUpdate(Network network, DbClient dbClient) {
List<Network> networks = new ArrayList<Network>();
if (network.getRoutedNetworks() != null && !network.getRoutedNetworks().isEmpty()) {
Iterator<Network> networksItr = dbClient.queryIterativeObjects(Network.class,
StringSetUtil.stringSetToUriList(
network.getRoutedNetworks()));
while (networksItr.hasNext()) {
Network net = networksItr.next();
if (net != null) {
networks.add(net);
}
}
}
_log.debug("Found {} routed networks for network {}",
networks.size(), network.getId());
return networks;
}
}