/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.networkcontroller.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.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus;
import com.emc.storageos.db.client.model.FCEndpoint;
import com.emc.storageos.db.client.model.Network;
import com.emc.storageos.db.client.model.NetworkSystem;
import com.emc.storageos.db.client.model.StorageProtocol.Transport;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator;
/**
* This is a utility class that is used to handle reconciliation of newly discovered
* transport zones from a given network system with those stored in the database. It
* is coded to provide the results back in the form of sets of added/removed/updated
* transport zone as opposed to updating the DB to improve testability.
*
* @author elalih
*
*/
public class TransportZoneReconciler {
private static String PREFIX_VSAN = "VSAN_";
private static String PREFIX_FABRIC = "FABRIC_";
private static final Logger _log = LoggerFactory
.getLogger(TransportZoneReconciler.class);
/**
* Given the old transport zones and the new fabrics and end points
* returns the lists of what needs to be updated, deleted and added.
*
* @param network - The network system being refreshed
* @param iEndPoints - the iterator of new end points
* @param fabricIdsMap - the list of all fabrics on the network system
* @param oldTransportZones - the old transport zones
* @throws Exception
* @return A results object containing lists of added, removed and modified
* transport zone
*/
public Results reconcile(NetworkSystem network, Iterator<FCEndpoint> iEndPoints,
Map<String, String> fabricIdsMap,
List<Network> oldTransportZones) throws Exception {
// compute the new transport zones for the network system being
// refreshed from the end points
HashMap<String, Network> newTransportZones = getNewTransportZones(
network, iEndPoints, fabricIdsMap);
_log.info("Reconciling {} new networks with " +
"the existing {} networks", newTransportZones.size(),
oldTransportZones.size());
// a list to keep track of used names - needed when generating new names
// to avoid duplications
List<String> existingTransportZoneNames = new ArrayList<String>();
// This is the map used to track what has not been accounted for - i.e.
// new transport zones
HashMap<String, Network> mutableMap = new HashMap<String, Network>(
newTransportZones);
Results results = new Results();
// reconcile
for (Network transportZone : oldTransportZones) {
existingTransportZoneNames.add(transportZone.getLabel());
if (transportZone.getDiscovered() == true) {
String fabricWwn = parseWWNFromGuid(transportZone
.getNativeGuid());
if (newTransportZones.containsKey(fabricWwn)) {
_log.info("Checking if existing network {} " +
" needs updating.", transportZone.getLabel());
handleUpdatedTransportZone(network, transportZone,
newTransportZones.get(fabricWwn), results);
mutableMap.remove(fabricWwn);
} else if (transportZone.getNetworkSystems().contains(
network.getId().toString())) {
_log.info("Existing network is not found." +
" Checking if it is removed or modified",
transportZone.getLabel());
Network newTZ = handleRemovedTransportZone(
transportZone, network.getId(), newTransportZones,
oldTransportZones, results);
if (newTZ != null) { // we figured the old transport zone has changed WWN
// so we're using one and throwing away one
mutableMap.remove(NetworkUtil.getNetworkWwn(newTZ));
}
}
}
}
// now I deal with anything that is left in the map - These are new
// transport zones
for (String wwn : mutableMap.keySet()) {
Network newTZ = mutableMap.get(wwn);
newTZ.setLabel(getUniqueTransportZoneName(newTZ.getLabel(),
existingTransportZoneNames));
results.getAdded().add(newTZ);
}
return results;
}
private HashMap<String, Network> getNewTransportZones(
NetworkSystem network, Iterator<FCEndpoint> iEndPoints,
Map<String, String> fabricIdsMap) throws Exception {
HashMap<String, Network> newTransportZones = new HashMap<String, Network>();
// first create empty transport zones
Network tz = null;
for (String wwn : fabricIdsMap.keySet()) {
tz = createTransportZone(network, wwn, fabricIdsMap.get(wwn));
newTransportZones.put(wwn, tz);
}
while (iEndPoints.hasNext()) {
FCEndpoint endpoint = iEndPoints.next();
if (endpoint == null || endpoint.getInactive()
// this should never happen
|| !newTransportZones.containsKey(endpoint.getFabricWwn())) {
continue;
}
newTransportZones.get(endpoint.getFabricWwn()).addEndpoints(
Collections.singletonList(endpoint.getRemotePortName()
.toUpperCase()), true);
}
return newTransportZones;
}
private String getUniqueTransportZoneName(String label,
List<String> existingTransportZoneNames) {
if (!existingTransportZoneNames.contains(label)) {
return label; // it is already a unique name
} else {
for (int i = 1; i < 100; i++) {
String newLabel = label + "(" + i + ")";
if (!existingTransportZoneNames.contains(newLabel)) {
return newLabel;
}
}
}
return null;
}
private void handleUpdatedTransportZone(NetworkSystem networkSystem,
Network oldTransportZone, Network newTransportZone, Results results)
throws IOException {
List<String> removedEndPoints = new ArrayList<String>();
List<String> addedEndPoints = new ArrayList<String>(
newTransportZone.retrieveEndpoints());
StringSet newEndPoints = newTransportZone.retrieveEndpoints();
for (String endpoint : oldTransportZone.retrieveEndpoints()) {
if (newEndPoints.contains(endpoint)) {
addedEndPoints.remove(endpoint); // The endpoint still present, account for that
} else if (oldTransportZone.endpointIsDiscovered(endpoint)) {
removedEndPoints.add(endpoint);
}
}
if (!addedEndPoints.isEmpty()) {
oldTransportZone.addEndpoints(addedEndPoints, true);
// update the results object
results.getAddedEndPoints().put(oldTransportZone, addedEndPoints);
_log.info("Endpoints {} were added to fabric {} on network {}",
new Object[] { addedEndPoints.toArray(), oldTransportZone.getNativeId(), oldTransportZone.getId().toString() });
}
if (!removedEndPoints.isEmpty()) {
oldTransportZone.removeEndpoints(removedEndPoints);
// update the results object
results.getRemovedEndPoints().put(oldTransportZone, removedEndPoints);
_log.info("Endpoints {} were removed from fabric {} on network {}",
new Object[] { removedEndPoints.toArray(), oldTransportZone.getNativeId(), oldTransportZone.getId().toString() });
}
if (!oldTransportZone.getNetworkSystems().contains(
networkSystem.getId().toString())) {
oldTransportZone.addNetworkSystems(Collections
.singletonList(networkSystem.getId().toString()));
if (RegistrationStatus.REGISTERED.name().equalsIgnoreCase(networkSystem.getRegistrationStatus())) {
oldTransportZone.setRegistrationStatus(RegistrationStatus.REGISTERED.name());
}
}
if (!oldTransportZone.getNativeId().equals(
newTransportZone.getNativeId())) {
oldTransportZone.setNativeId(newTransportZone.getNativeId());
}
// update the results object
results.getModified().add(oldTransportZone);
}
/**
* This function handles transport zones that were seen on the network
* system before but cannot be found by fabric WWN search in the new list of
* transport zone. The possible scenario are :
* <ol>
* <li>The fabric was indeed removed from the network system</li>
* <li>The fabric still exists but under a new WWN (case when principal switch changes or fragmentation)</li>
* <li>The fabric was discovered while fragmented and assumed to be an isolated fabric but now it is merging again and so, for all
* practical purposes, the old WWN should be removed</li>
* </ol>
*
* @param tzone
* - the zone that cannot be matched by WWN to a new zone
* @param networkUri
* - the URI of the network system
* @param newTransportZones
* - The collection of transport zones discovered on the network system
* @param oldTransportZones
* - The collection of all transport zones discovered and active
* @return The new transport zone that has been determined to be the match of tzone.
* @throws IOException
*/
private Network handleRemovedTransportZone(Network tzone,
URI networkUri, HashMap<String, Network> newTransportZones,
Collection<Network> oldTransportZones, Results results) throws IOException {
Network newTransportZone = null;
String uri = networkUri.toString();
if (tzone.getNetworkSystems().contains(uri)) {
// first we check if it is removed, fragmented or has a new WWN
// If we find a network with the same fabricId, the network either merging and has new WWN
newTransportZone = findByFabricId(tzone, newTransportZones);
if (newTransportZone == null) { // this transport zone no longer exists
_log.info("Existing network {} did not match any in the " +
" new networks by name or by WWN. It must have been removed.",
tzone.getLabel());
results.getRemoved().add(tzone);
} else { // new principal switch? merging?
_log.info("Existing network {} matches {} in the " +
" new networks by name. Reconciling the two.",
tzone.getLabel(), newTransportZone.getLabel());
// find if an existing network matched by fabricId and WWN
Network oldTransportZone = findMatchByFabricIdAndWwn(newTransportZone, oldTransportZones);
if (oldTransportZone != null) {
_log.info("The fabric must have fragmented and is now merging." +
"User changes will be merged also any endpoints found will be added.");
// merge user changes to the other fragment and remove from this one so it can be deleted
oldTransportZone.addEndpoints(getUserCreatedEndPoints(tzone), false);
tzone.removeEndpoints(getUserCreatedEndPoints(tzone));
if (tzone.getAssignedVirtualArrays() != null) {
oldTransportZone.addAssignedVirtualArrays(tzone.getAssignedVirtualArrays());
tzone.removeAssignedVirtualArrays(tzone.getAssignedVirtualArrays());
}
// remove the fragment
results.getRemoved().add(tzone);
// we are removing the old - the new one is NOT a match
newTransportZone = null;
} else { // either fragmenting or principal switch change -
// Update the old transportZone
_log.info("Either the fabric is fragmenting or the principal " +
"switch has changed. Adding new endpoints and keeping old ones.");
mergeNetworks(tzone, newTransportZone);
results.getModified().add(tzone);
}
}
}
return newTransportZone;
}
/**
* Update one network with information from another
*
* @param tzone the target network to be updates
* @param sZone the source network
*/
private void mergeNetworks(Network tzone, Network sZone) {
List<String> addedEndPoints = new ArrayList<String>(
sZone.retrieveEndpoints());
StringSet newEndPoints = sZone.retrieveEndpoints();
for (String endpoint : tzone.retrieveEndpoints()) {
if (newEndPoints.contains(endpoint)) {
// the end point still present - account for that
addedEndPoints.remove(endpoint);
}
}
if (!addedEndPoints.isEmpty()) {
// only add, do not remove because things are not stable
tzone.addEndpoints(addedEndPoints, true);
}
tzone.setNativeGuid(sZone.getNativeGuid());
}
/**
* We have a network discovered on the network system and we want
* to find the existing matching network
*
* @param newTransportZone
* @param oldTransportZones
* @return
*/
private Network findMatchByFabricIdAndWwn(Network newTransportZone,
Collection<Network> oldTransportZones) {
for (Network zone : oldTransportZones) {
if (newTransportZone.getNativeId().equals(zone.getNativeId())
&& newTransportZone.getNativeGuid().equals(
zone.getNativeGuid())) {
return zone;
}
}
return null;
}
private String parseWWNFromGuid(String guid) {
String[] splitGuid = guid.split("\\+");
if (splitGuid.length == 3) {
return splitGuid[2];
}
return "";
}
private Network createTransportZone(NetworkSystem network,
String fabricWwn, String fabricId) {
Network newTZ = new Network();
String nativeGuid = NativeGUIDGenerator
.generateTransportZoneNativeGuid(Transport.FC.toString(),
network.getSystemType(), fabricWwn);
String prefix = network.getSystemType().equals(NetworkSystem.Type.mds.toString()) ? PREFIX_VSAN
: PREFIX_FABRIC;
newTZ.setId(URIUtil.createId(Network.class));
newTZ.setLabel(prefix + fabricId);
newTZ.setNativeId(fabricId);
newTZ.setTransportType(Transport.FC.toString());
newTZ.setDiscovered(true);
newTZ.setNativeGuid(nativeGuid);
newTZ.setNetworkSystems(new StringSet());
newTZ.getNetworkSystems().add(network.getId().toString());
newTZ.setRegistrationStatus(network.getRegistrationStatus());
return newTZ;
}
private Network findByFabricId(Network tzone,
HashMap<String, Network> newTransportZones) {
for (Network newTz : newTransportZones.values()) {
if (newTz.getNativeId().equals(tzone.getNativeId())) {
return newTz;
}
}
return null;
}
/**
* Finds and returns the user-added endpoints in a network
*
* @param tzone the network
* @return a list of user-added endpoints in the network. An empty list
* if no user-added endpoints were found.
*/
public static List<String> getUserCreatedEndPoints(Network tzone) {
List<String> endPoints = new ArrayList<String>();
for (String endPoint : tzone.retrieveEndpoints()) {
if (tzone.getEndpointsMap().get(endPoint).equals(Boolean.FALSE.toString())) {
endPoints.add(endPoint);
}
}
return endPoints;
}
class Results {
private List<Network> added = new ArrayList<Network>();
private List<Network> removed = new ArrayList<Network>();
private List<Network> modified = new ArrayList<Network>();
private Map<Network, List<String>> addedEndPoints = new HashMap<Network, List<String>>();
private Map<Network, List<String>> removedEndPoints = new HashMap<Network, List<String>>();
public List<Network> getAdded() {
return added;
}
public List<Network> getRemoved() {
return removed;
}
public List<Network> getModified() {
return modified;
}
public Map<Network, List<String>> getAddedEndPoints() {
return addedEndPoints;
}
public Map<Network, List<String>> getRemovedEndPoints() {
return removedEndPoints;
}
public List<Network> getAddedAndModified() {
List<Network> changedNetworks = new ArrayList<Network>(added);
changedNetworks.addAll(modified);
return changedNetworks;
}
}
}