/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.networkcontroller.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.model.NetworkSystem;
import com.emc.storageos.db.client.util.WWNUtility;
import com.emc.storageos.networkcontroller.exceptions.NetworkDeviceControllerException;
import com.emc.storageos.networkcontroller.impl.mds.Zone;
import com.emc.storageos.networkcontroller.impl.mds.ZoneMember;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.volumecontroller.impl.BiosCommandResult;
import com.google.common.collect.Sets;
public abstract class NetworkSystemDeviceImpl implements NetworkSystemDevice {
private static final Logger _log = LoggerFactory.getLogger(NetworkSystemDeviceImpl.class);
/**
* Given a list of zones requested to be added, and a list of existing zones on
* the device, return a list of zones that should really be added.
* Zones which already exist will not be added again (if they match by content and
* name). Zones with match members but have a different name (and are therefore
* assumed to be externally created) are ignored.
*
* @param zones -- Zones requested to be created.
* @param existingZones -- Existing zones on the device.
* @return -- Zones to be added
* @throws NetworkDeviceControllerException
*/
protected List<Zone> getZonesToBeAdded(List<Zone> zones, List<Zone> existingZones)
throws NetworkDeviceControllerException {
// Now, we want to add only new zones.
// Zones that have members completely in a zone in the active zoneset already are
// ignored, if they match the zoneset name we have.
// Make an array of sets containing the members of the active zones.
// The next paragraph below checks each zone to be created against each existing zone and looks for matches.
Map<String, Set<String>> memberSetMap = new HashMap<String, Set<String>>(); // key is an existing zone name
for (Zone azone : existingZones) {
Set<String> memberAddressSet = new HashSet<String>();
for (ZoneMember amember : azone.getMembers()) {
if (amember.getAddress() != null) {
memberAddressSet.add(amember.getAddress());
}
if (amember.getAlias() != null) {
memberAddressSet.add(amember.getAlias());
}
}
memberSetMap.put(azone.getName(), memberAddressSet);
}
// Check each zone to be added to see if it's matched by an active set.
List<Zone> zonesToBeAdded = new ArrayList<Zone>();
for (Zone zone : zones) {
boolean addIt = true;
for (String azoneName : memberSetMap.keySet()) {
Set<String> activeSet = memberSetMap.get(azoneName);
boolean match = true;
for (ZoneMember member : zone.getMembers()) {
if ((member.getAddress() != null && !WWNUtility.isValidWWN(member.getAddress())) ||
(member.getAlias() != null && !WWNUtility.isValidWWNAlias(member.getAlias()))) {
throw NetworkDeviceControllerException.exceptions.getZonesToBeAddedFailedIllegalAddress(
member.getAddress());
}
if ((member.getAddress() != null && !activeSet.contains(member.getAddress())) ||
(member.getAlias() != null && !activeSet.contains(member.getAlias()))) {
match = false;
}
}
if (match == true) {
// Check to see if it's the same zone name that we wanted. Otherwise, log a duplicate zone.
if (azoneName.equals(zone.getName())) {
addIt = false;
} else {
_log.info("Found duplicate zone: " + azoneName + " for: " + zone.getName() + " ... ignoring");
}
}
}
if (addIt) {
zonesToBeAdded.add(zone);
}
}
return zonesToBeAdded;
}
/**
* Given a list of zones requested to be deleted, and a list of existing zones on the device,
* this returns a list of zones to actually be deleted, along with a count of the number
* of remaining zones after they are deleted.
*
* @param zones -- Zones requested to be deleted
* @param existingZones -- on the device
* @param remainingZones -- Integer[1] array that returns number of remaining zones
* @return collection of zones to be deleted
* @throws NetworkDeviceControllerException
*/
protected List<Zone> getZonesToBeDeleted(List<Zone> zones, Collection<Zone> existingZones,
Integer[] remainingZones, Map<String, String> removedZoneResults)
throws NetworkDeviceControllerException {
// Zones that have members completely in a zone in the active zoneset already are
// to be deleted.
// Make a map of zone names to zones found on the switch.
Map<String, Zone> activeZonesMap = new HashMap<String, Zone>();
Map<String, Object> cimObjectPaths = new HashMap<String, Object>();
for (Zone azone : existingZones) {
if (azone != null) {
activeZonesMap.put(azone.getName(), azone);
cimObjectPaths.put(azone.getName(), azone.getCimObjectPath());
}
}
remainingZones[0] = new Integer(activeZonesMap.size());
// Check each zone to be deleted to see if it's matched by a zone in the active set.
List<Zone> zonesToBeDeleted = new ArrayList<Zone>();
for (Zone zone : zones) {
Zone zoneInFabric = activeZonesMap.get(zone.getName());
if (zoneInFabric != null) {
boolean match = true;
if (zone.getMembers() != null && !zone.getMembers().isEmpty()) {
match = sameMembers(zoneInFabric, zone);
}
if (match) {
Object cimObjectPath = cimObjectPaths.get(zone.getName());
zone.setCimObjectPath(cimObjectPath);
zonesToBeDeleted.add(zone);
remainingZones[0]--;
} else {
removedZoneResults.put(zone.getName(), ERROR + " : The existing zone members do not match what is in the request.");
_log.info("Zone " + zone.getName() + " was found but the members did not match");
}
} else {
_log.info("Zone " + zone.getName() + " was not found in the active zone set. Nothing to do.");
removedZoneResults.put(zone.getName(), NO_CHANGE);
}
}
return zonesToBeDeleted;
}
@Override
public Set<String> getRoutedEndpoints(NetworkSystem networkSystem,
String fabricId, String fabricWwn) throws Exception {
return Collections.EMPTY_SET;
}
/**
* Create default zoneset for a given fabricId
*
* @param fabricId
* @return default zoneset name for given fabric
*/
abstract protected String getDefaultZonesetName(String fabricId);
/**
* Convenient method to handle exception while zoning
*
* @param ex
* @param activateZones
*/
protected void handleZonesStrategyException(Exception ex, boolean activateZones) throws Exception {
if (activateZones) { // immediate activation means we expect all zones to be successful
throw ex; // we should fail if any zone fails
} else {
_log.info("Exception was encountered but will try the rest of the batch. " +
"Error message: " + ex.getMessage());
}
}
/**
* Get zone member addresses that are not mapped from device alias database.
*
* @param zone
* @return list of zone members
*/
protected Collection<String> getWwnsInZone(Zone zone) {
Set<String> col = Sets.newHashSet();
if (zone.getMembers() != null) {
for (ZoneMember member : zone.getMembers()) {
if (!StringUtils.isEmpty(member.getAddress()) && !member.isAliasType()) {
col.add(member.getAddress());
}
}
}
return col;
}
/**
* Get zone members alias
*
* @param zone
* @return list of zone members alias
*/
protected Collection<String> getAliasesInZone(Zone zone) {
Set<String> col = Sets.newHashSet();
if (zone.getMembers() != null) {
for (ZoneMember member : zone.getMembers()) {
if (!StringUtils.isEmpty(member.getAlias()) && member.isAliasType()) {
col.add(member.getAlias());
}
}
}
return col;
}
/**
* Returns true if at least item in the results matches the desired result.
*
* @param results
* @return true if at least one item has the desired result
*/
protected boolean hasResult(Map<String, String> results, String result) {
if (results != null) {
for (String str : results.values()) {
if (str.startsWith(result)) {
return true;
}
}
}
return false;
}
/**
* Returns a formatted string of the results.
*
* @param results
* @return
*/
protected String toMessage(Map<String, String> results) {
StringBuilder builder = new StringBuilder();
if (results != null) {
for (Map.Entry<String, String> entry : results.entrySet()) {
builder.append(entry.getKey() + ": " + entry.getValue() + ";\n");
}
}
return builder.toString();
}
protected BiosCommandResult getBiosCommandResult(Map<String, String> results) {
BiosCommandResult result = null;
if (hasResult(results, ERROR)) {
ServiceError serviceError = NetworkDeviceControllerException.errors.batchOperationFailed(toMessage(results));
result = BiosCommandResult.createErrorResult(serviceError);
} else {
result = BiosCommandResult.createSuccessfulResult();
}
result.setObjectList(Collections.singletonList((Object) results));
return result;
}
/**
* Check if 2 zones has the same members
*
* @param zoneInFabric
* @param zone
* @return true if both zone has the same wwn and alias members
*/
protected boolean sameMembers(Zone zoneInFabric, Zone zone) {
boolean same = true;
if (zoneInFabric.getMembers().size() == zone.getMembers().size()) {
Collection<String> wwnsInFabric = getWwnsInZone(zoneInFabric);
Collection<String> aliasesInFabric = getAliasesInZone(zoneInFabric);
for (ZoneMember member : zone.getMembers()) {
if (!StringUtils.isEmpty(member.getAddress()) && !wwnsInFabric.contains(member.getAddress())) {
_log.info("Zone member WWN {} not found in active zone {}", member.getAddress(), zone.getName());
same = false;
break;
} else if (!StringUtils.isEmpty(member.getAlias()) && !aliasesInFabric.contains(member.getAlias())) {
_log.info("Zone member alias {} not found in active zone {}", member.getAlias(), zone.getName());
same = false;
break;
}
}
} else {
same = false;
}
// if zones do not have same info, log their info for debugging.
if (!same) {
_log.info("Zones {} and {} do not have the same info", zoneInFabric.getName(), zone.getName());
_log.info("Zone {} has {} members in fabric.", zoneInFabric.getName(), zoneInFabric.getMembers().size());
_log.info("and intended zone {} has {} members.", zone.getName(), zone.getMembers().size());
zoneInFabric.print();
zone.print();
}
return same;
}
}