/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.util;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
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.BlockMirror;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DataObject.Flag;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.DiscoveryStatus;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportGroup.ExportGroupType;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.ExportPathParams;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.HostInterface.Protocol;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.OpStatusMap;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.util.CommonTransformerFunctions;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.DataObjectUtils;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.StringMapUtil;
import com.emc.storageos.db.client.util.StringSetUtil;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
import com.google.common.collect.TreeMultimap;
public class ExportUtils {
// Logger
private final static Logger _log = LoggerFactory.getLogger(ExportUtils.class);
public static final String NO_VIPR = "NO_VIPR"; // used to exclude VIPR use of export mask
private static final String MAX_ALLOWED_HLU_KEY = "controller_%s_max_allowed_HLU";
// System Property. If this is false, it's OK to run validation checks, but don't fail out when they fail.
// This may be a dangerous thing to do, so we see this as a "kill switch" when service is in a desperate
// situation and they need to disable the feature.
private static final String VALIDATION_CHECK_PROPERTY = "validation_check";
private static CoordinatorClient coordinator;
public CoordinatorClient getCoordinator() {
return coordinator;
}
public void setCoordinator(CoordinatorClient coordinator) {
ExportUtils.coordinator = coordinator;
}
/**
* Get an initiator as specified by the initiator's network port.
*
* @param networkPort The initiator's port WWN or IQN.
* @return A reference to an initiator.
*/
public static Initiator getInitiator(String networkPort, DbClient dbClient) {
Initiator initiator = null;
URIQueryResultList resultsList = new URIQueryResultList();
// find the initiator
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getInitiatorPortInitiatorConstraint(
networkPort), resultsList);
Iterator<URI> resultsIter = resultsList.iterator();
while (resultsIter.hasNext()) {
initiator = dbClient.queryObject(Initiator.class, resultsIter.next());
// there should be one initiator, so return as soon as it is found
if (initiator != null && !initiator.getInactive()) {
return initiator;
}
}
return null;
}
/**
* Return a Set initiators for the given collection of port names (WWN or IQNs)
*
* @param portNames [IN] - Port names to query
* @param dbClient [IN] - DbClient for DB access
* @return Set or Initiators looked up by the 'portNames'
*/
public static Set<Initiator> getInitiators(Collection<String> portNames, DbClient dbClient) {
Set<Initiator> initiatorSet = new HashSet<>();
for (String portName : portNames) {
Initiator initiator = getInitiator(Initiator.toPortNetworkId(portName), dbClient);
if (initiator != null) {
initiatorSet.add(initiator);
}
}
return initiatorSet;
}
/**
* A utility function method to get the user-created initiators from an export mask.
* If an initiator is not found for a given user-created WWN, it is simply
* ignored and no error is raised.
*
* @param exportMask the export mask
* @param dbClient an instance of DbClient
* @return a list of Initiators
*/
public static List<Initiator> getExportMaskExistingInitiators(ExportMask exportMask, DbClient dbClient) {
List<Initiator> initiators = new ArrayList<Initiator>();
Initiator initiator = null;
if (exportMask.getExistingInitiators() != null &&
!exportMask.getExistingInitiators().isEmpty()) {
for (String initStr : exportMask.getExistingInitiators()) {
initStr = Initiator.toPortNetworkId(initStr);
initiator = getInitiator(initStr, dbClient);
if (initiator != null) {
initiators.add(initiator);
}
}
}
return initiators;
}
/**
* Fetches and returns the initiators for an export mask. If the ExportMask's
* existing initiators are set, they will also be returned if an instance can
* be found in ViPR for the given initiator port id.
*
* @param exportMask the export mask
* @param dbClient an instance of {@link DbClient}
* @return a list of active initiators in the export mask
*/
public static List<URI> getExportMaskAllInitiators(ExportMask exportMask, DbClient dbClient) {
List<URI> initiators = new ArrayList<URI>();
if (exportMask.getInitiators() != null &&
!exportMask.getInitiators().isEmpty()) {
initiators.addAll(StringSetUtil.stringSetToUriList(exportMask.getInitiators()));
}
if (exportMask.getExistingInitiators() != null &&
!exportMask.getExistingInitiators().isEmpty()) {
for (String initStr : exportMask.getExistingInitiators()) {
initStr = Initiator.toPortNetworkId(initStr);
Initiator init = getInitiator(initStr, dbClient);
if (init != null && !initiators.contains(init.getId())) {
initiators.add(init.getId());
}
}
}
return initiators;
}
/**
* Fetches and returns the initiators for one or more export masks.
*
* @param exportMaskUris the export mask URIs
* @param dbClient an instance of {@link DbClient}
* @return a list of active initiators in the export mask
*/
public static List<Initiator> getExportMasksInitiators(Collection<URI> exportMaskUris, DbClient dbClient) {
List<Initiator> list = new ArrayList<Initiator>();
for (URI exportMaskUri : exportMaskUris) {
list.addAll(getExportMaskInitiators(exportMaskUri, dbClient));
}
return list;
}
/**
* Get all initiator ports in mask.
*
* @param exportMask
* @param dbClient
* @return
*/
public static Set<String> getExportMaskAllInitiatorPorts(ExportMask exportMask, DbClient dbClient) {
Set<String> ports = new HashSet<String>();
if (exportMask.getInitiators() != null && !exportMask.getInitiators().isEmpty()) {
List<URI> iniUris = StringSetUtil.stringSetToUriList(exportMask.getInitiators());
List<Initiator> initiators = dbClient.queryObject(Initiator.class, iniUris);
for (Initiator ini : initiators) {
if (ini == null || ini.getInitiatorPort() == null) {
continue;
}
ports.add(Initiator.normalizePort(ini.getInitiatorPort()));
}
}
if (exportMask.getExistingInitiators() != null && !exportMask.getExistingInitiators().isEmpty()) {
for (String initStr : exportMask.getExistingInitiators()) {
ports.add(initStr);
}
}
return ports;
}
/**
* Fetches and returns the initiators for an export mask.
*
* @param exportMaskUri the export mask URI
* @param dbClient an instance of {@link DbClient}
* @return a list of active initiators in the export mask
*/
public static List<Initiator> getExportMaskInitiators(URI exportMaskUri, DbClient dbClient) {
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskUri);
return getExportMaskInitiators(exportMask, dbClient);
}
/**
* Fetches and returns the initiators for an export mask. If the ExportMask's
* existing initiators are set, they will also be returned if an instance can
* be found in ViPR for the given initiator port id.
*
* @param exportMask the export mask
* @param dbClient an instance of {@link DbClient}
* @return a list of active initiators in the export mask
*/
public static List<Initiator> getExportMaskInitiators(ExportMask exportMask, DbClient dbClient) {
if (exportMask != null && exportMask.getInitiators() != null) {
List<URI> initiators = StringSetUtil.stringSetToUriList(exportMask.getInitiators());
return dbClient.queryObject(Initiator.class, initiators);
}
return new ArrayList<Initiator>();
}
/**
* Fetches and returns the initiators for an export group.
*
* @param exportGroup the export grop
* @param dbClient an instance of {@link DbClient}
* @return a list of active initiators in the export mask
*/
public static List<Initiator> getExportGroupInitiators(ExportGroup exportGroup, DbClient dbClient) {
List<URI> initiators = StringSetUtil.stringSetToUriList(exportGroup.getInitiators());
return dbClient.queryObject(Initiator.class, initiators);
}
/**
* Return the storage ports allocated to each initiators in an export mask by looking
* up the zoningMap.
*
* @param mask
* @param initiator
* @return
*/
public static List<URI> getInitiatorPortsInMask(ExportMask mask, Initiator initiator, DbClient dbClient) {
List<URI> list = new ArrayList<URI>();
StringSetMap zoningMap = mask.getZoningMap();
String strUri = initiator.getId().toString();
if (zoningMap != null && zoningMap.containsKey(strUri) &&
initiator.getProtocol().equals(Protocol.FC.toString())) {
list = StringSetUtil.stringSetToUriList(zoningMap.get(strUri));
}
_log.info("getInitiatorPortsInMask {} {}", initiator, Joiner.on(',').join(list));
return list;
}
/**
* Returns the storage ports allocated to each initiator based
* on the connectivity between them.
*
* @param mask
* @param initiator
* @param dbClient
* @return
*/
public static List<URI> getPortsInInitiatorNetwork(ExportMask mask, Initiator initiator, DbClient dbClient) {
List<URI> list = new ArrayList<URI>();
List<StoragePort> ports = getStoragePorts(mask, dbClient);
NetworkLite networkLite = NetworkUtil.getEndpointNetworkLite(initiator.getInitiatorPort(), dbClient);
if (networkLite != null) {
for (StoragePort port : ports) {
if (port.getNetwork() != null &&
port.getNetwork().equals(networkLite.getId())) {
list.add(port.getId());
}
}
if (list.isEmpty() && networkLite.getRoutedNetworks() != null) {
for (StoragePort port : ports) {
if (port.getNetwork() != null &&
networkLite.getRoutedNetworks().contains(port.getNetwork().toString())) {
list.add(port.getId());
}
}
}
}
return list;
}
/**
* Fetches and returns the storage ports for an export mask
*
* @param exportMask the export mask
* @param dbClient an instance of {@link DbClient}
* @return a list of active storage ports used by the export mask
*/
public static List<StoragePort> getStoragePorts(ExportMask exportMask, DbClient dbClient) {
List<StoragePort> ports = new ArrayList<StoragePort>();
if (exportMask.getStoragePorts() != null) {
StoragePort port = null;
for (String initUri : exportMask.getStoragePorts()) {
port = dbClient.queryObject(StoragePort.class, URI.create(initUri));
if (port != null && !port.getInactive()) {
ports.add(port);
}
}
}
_log.info("Found {} storage ports in export mask {}", ports.size(), exportMask.getMaskName());
return ports;
}
/**
* Creates a map of storage ports keyed by the port WWN.
*
* @param ports the storage ports
*
* @return a map of portWwn-to-port of storage ports
*/
public static Map<String, StoragePort> getStoragePortsByWwnMap(Collection<StoragePort> ports) {
Map<String, StoragePort> map = new HashMap<String, StoragePort>();
for (StoragePort port : ports) {
map.put(port.getPortNetworkId(), port);
}
return map;
}
/**
* Fetches all the export masks in which a block object is member
*
* @param blockObject the block object
* @param dbClient an instance of {@link DbClient}
* @return a list of export masks in which a block object is member
*/
public static Map<ExportMask, ExportGroup> getExportMasks(BlockObject blockObject, DbClient dbClient) {
Map<ExportMask, ExportGroup> exportMasksMap = new HashMap<ExportMask, ExportGroup>();
URIQueryResultList exportGroups = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(blockObject.getId()), exportGroups);
for (URI egUri : exportGroups) {
ExportGroup exportGroup = dbClient.queryObject(ExportGroup.class, egUri);
if (exportGroup.getInactive()) {
continue;
}
for (ExportMask exportMask : ExportMaskUtils.getExportMasks(dbClient, exportGroup)) {
if (exportMask != null && !exportMask.getInactive() && exportMask
.getStorageDevice().equals(blockObject.getStorageController())
&& exportMask.hasVolume(blockObject.getId())
&& exportMask.getInitiators() != null && exportMask.getStoragePorts() != null) {
exportMasksMap.put(exportMask, exportGroup);
}
}
}
_log.info("Found {} export masks for block object {}", exportMasksMap.size(), blockObject.getLabel());
return exportMasksMap;
}
/**
* Gets all the export masks that this initiator is member of.
*
* @param initiator the initiator
* @param dbClient an instance of {@link DbClient}
* @return all the export masks that this initiator is member of
*/
public static List<ExportMask> getInitiatorExportMasks(
Initiator initiator, DbClient dbClient) {
List<ExportMask> exportMasks = new ArrayList<ExportMask>();
URIQueryResultList egUris = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getExportGroupInitiatorConstraint(initiator.getId().toString()), egUris);
ExportGroup exportGroup = null;
for (URI egUri : egUris) {
exportGroup = dbClient.queryObject(ExportGroup.class, egUri);
if (exportGroup == null || exportGroup.getInactive() || exportGroup.getExportMasks() == null) {
continue;
}
List<ExportMask> masks = ExportMaskUtils.getExportMasks(dbClient, exportGroup);
for (ExportMask mask : masks) {
if (mask != null &&
!mask.getInactive() &&
mask.hasInitiator(initiator.getId().toString()) &&
mask.getVolumes() != null &&
mask.getStoragePorts() != null) {
exportMasks.add(mask);
}
}
}
_log.info("Found {} export masks for initiator {}", exportMasks.size(), initiator.getInitiatorPort());
return exportMasks;
}
/**
* Returns all the ExportGroups the initiator is a member of.
*
* @param initiator Initiator
* @param dbClient
* @return List<ExportGroup> that contain a key to the Initiator URI
*/
public static List<ExportGroup> getInitiatorExportGroups(
Initiator initiator, DbClient dbClient) {
List<ExportGroup> exportGroups = new ArrayList<ExportGroup>();
URIQueryResultList egUris = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getExportGroupInitiatorConstraint(initiator.getId().toString()), egUris);
ExportGroup exportGroup = null;
for (URI egUri : egUris) {
exportGroup = dbClient.queryObject(ExportGroup.class, egUri);
if (exportGroup == null || exportGroup.getInactive() || exportGroup.getExportMasks() == null) {
continue;
}
exportGroups.add(exportGroup);
}
return exportGroups;
}
/**
* Returns all the ExportGroups the initiator and volume/snapshot is a member of.
*
* @param initiator Initiator
* @param blockObjectId ID of a volume or snapshot
* @param dbClient db client handle
* @return List<ExportGroup> that contain a key to the Initiator URI
*/
public static List<ExportGroup> getInitiatorVolumeExportGroups(
Initiator initiator, URI blockObjectId, DbClient dbClient) {
List<ExportGroup> exportGroups = new ArrayList<ExportGroup>();
URIQueryResultList egUris = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getExportGroupInitiatorConstraint(initiator.getId().toString()), egUris);
for (URI egUri : egUris) {
ExportGroup exportGroup = dbClient.queryObject(ExportGroup.class, egUri);
if (exportGroup == null || exportGroup.getInactive() || exportGroup.getExportMasks() == null) {
continue;
}
if (exportGroup.hasBlockObject(blockObjectId)) {
exportGroups.add(exportGroup);
}
}
return exportGroups;
}
/**
* Cleans up the export group references to the export mask and volumes therein
*
* @param exportMask export mask
*/
public static void cleanupAssociatedMaskResources(DbClient dbClient, ExportMask exportMask) {
List<ExportGroup> exportGroups = ExportMaskUtils.getExportGroups(dbClient, exportMask);
if (exportGroups != null) {
// Remove the mask references in the export group
for (ExportGroup exportGroup : exportGroups) {
// Remove this mask from the export group
exportGroup.removeExportMask(exportMask.getId().toString());
}
// Update all of the export groups in the DB
dbClient.updateObject(exportGroups);
}
}
/**
* Create a TreeMultimap that gives a mapping of a volume URI String to a list of
* ExportMasks that the volume is associated with. All ExportMasks are associated
* with the ExportGroup.
*
* @param dbClient [in] - DB client object
* @param exportGroup [in] - ExportGroup object to use build the mapping
* @return Mapping of volume URI String to list of ExportMask URIs.
*/
private static TreeMultimap<String, URI> buildVolumesToExportMasksMap(DbClient dbClient,
ExportGroup exportGroup) {
TreeMultimap<String, URI> volumesToExportMasks = TreeMultimap.create();
List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(dbClient, exportGroup);
for (ExportMask exportMask : exportMasks) {
if (exportMask.getUserAddedVolumes() != null) {
for (String volUriStr : exportMask.getUserAddedVolumes().values()) {
volumesToExportMasks.put(volUriStr, exportMask.getId());
}
}
}
return volumesToExportMasks;
}
/**
* Routine will determine if the volume is associated with an ExportMask other than 'exportMask'.
*
* @param exportMask [in] ExportMask that is currently being validated
* @param volumeURIString [in] VolumeURI String
* @param volumesToExportMasks [in] - Used for checking the volume
* @return true if the Volume is in an ExportMask associated to ExportGroup
*/
private static boolean volumeIsInAnotherExportMask(ExportMask exportMask, String volumeURIString,
TreeMultimap<String, URI> volumesToExportMasks) {
boolean isInAnotherMask = false;
if (volumesToExportMasks.containsKey(volumeURIString)) {
// Create a temporary set (so that it can be modified)
Set<URI> exportMaskURIs = new HashSet<>(volumesToExportMasks.get(volumeURIString));
// Remove the 'exportMask' URI from the list. If anything is left, then it means that
// there is another ExportMask with the volume in it.
exportMaskURIs.remove(exportMask.getId());
isInAnotherMask = !exportMaskURIs.isEmpty();
}
return isInAnotherMask;
}
public static String getFileMountPoint(String fileStoragePort, String path) {
if (InetAddressUtils.isIPv6Address(fileStoragePort)) {
fileStoragePort = "[" + fileStoragePort + "]";
}
return fileStoragePort + ":" + path;
}
static public boolean isExportMaskShared(DbClient dbClient, URI exportMaskURI, Collection<URI> exportGroupURIs) {
List<ExportGroup> results = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, ExportGroup.class,
ContainmentConstraint.Factory.getConstraint(ExportGroup.class, "exportMasks", exportMaskURI));
int count = 0;
for (ExportGroup exportGroup : results) {
count++;
if (exportGroupURIs != null) {
exportGroupURIs.add(exportGroup.getId());
}
}
return count > 1;
}
/**
* This function is used to determine if an initiator is in an export mask other than the one
* being processed and this other export mask is used by a different export group yet they all
* are for the same host or compute resource. This situation happens when volumes in different
* virtual arrays but on the same storage array are exported to the same host. In this situation
* the application creates 2 export groups and 2 export masks in ViPR and 2 different masking
* views on the storage array, yet the masking views share the same initiator group.
* <p>
* This function checks that another export masks is not sharing the same initiator group but that is not under the same export group
* (this is handled elsewhere) by searching for an export mask that:
* <ol>
* <li>is for the same storage system</li>
* <li>is not one used by the same export group</li>
* </ol>
*
* @param dbClient an instance of DbClient
* @param initiatorUri the URI of the initiator being checked
* @param curExportMask the export mask being processed
* @param exportMaskURIs other export masks in the same export group as the export mask being processed.
* @return List of other shared masks name if the initiator is found in other export masks.
*/
public static List<String> getExportMasksSharingInitiator(DbClient dbClient, URI initiatorUri, ExportMask curExportMask,
Collection<URI> exportMaskURIs) {
List<ExportMask> results = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, ExportMask.class,
ContainmentConstraint.Factory.getConstraint(ExportMask.class, "initiators", initiatorUri));
List<String> sharedExportMaskNameList = new ArrayList<>();
for (ExportMask exportMask : results) {
if (exportMask != null && !exportMask.getId().equals(curExportMask.getId()) &&
exportMask.getStorageDevice().equals(curExportMask.getStorageDevice()) &&
!exportMaskURIs.contains(exportMask.getId())
&& StringSetUtil.areEqual(exportMask.getInitiators(), curExportMask.getInitiators())) {
_log.info(String.format("Initiator %s is shared with mask %s.",
initiatorUri, exportMask.getMaskName()));
sharedExportMaskNameList.add(exportMask.forDisplay());
}
}
return sharedExportMaskNameList;
}
/**
* Check if the initiator is being shared across masks and check if the mask has unmanaged volumes.
* This will return an error message to append to the caller's error message string.
*
* @param dbClient
* @param initiatorUri
* @param curExportMask
* @param exportMaskURIs
* @return error message to append
*/
public static String getExportMasksSharingInitiatorAndHasUnManagedVolumes(DbClient dbClient, Initiator initiator, ExportMask curExportMask,
Collection<URI> exportMaskURIs) {
List<ExportMask> results = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, ExportMask.class,
ContainmentConstraint.Factory.getConstraint(ExportMask.class, "initiators", initiator.getId()));
List<String> sharedExportMaskNameList = new ArrayList<>();
Set<String> unmanagedVolumeWWNs = new HashSet<>();
for (ExportMask exportMask : results) {
if (exportMask != null && !exportMask.getId().equals(curExportMask.getId()) &&
exportMask.getStorageDevice().equals(curExportMask.getStorageDevice()) &&
!exportMaskURIs.contains(exportMask.getId())
&& StringSetUtil.areEqual(exportMask.getInitiators(), curExportMask.getInitiators()) &&
exportMask.hasAnyExistingVolumes()) {
_log.info("Initiator {} is shared with mask {} and has unmanaged volumes",
initiator.getId(), exportMask.getMaskName());
sharedExportMaskNameList.add(exportMask.getMaskName());
unmanagedVolumeWWNs.addAll(exportMask.getExistingVolumes().keySet());
}
}
if (!sharedExportMaskNameList.isEmpty()) {
return String.format(" Initiator %s is shared between mask %s and other masks [%s] and has unmanaged volumes [%s]. Removing initiator will affect the other masking view",
Initiator.normalizePort(initiator.getInitiatorPort()), // initiator wwn
curExportMask.getMaskName(), // mask name being validated
Joiner.on(", ").join(sharedExportMaskNameList), // names of masks
(unmanagedVolumeWWNs.size() < 10) ? Joiner.on(", ").join(unmanagedVolumeWWNs) : "10 or more volumes"); // unmanaged volumes (up to 9)
}
return null;
}
/**
* Check if initiator is used by multiple masks of same storage array
*
* @param dbClient DbClient
* @param mask ExportMask
* @initiatorUri URI of initiator
* @return true if shared by multiple masks, otherwise false
*/
public static boolean isInitiatorSharedByMasks(DbClient dbClient, ExportMask mask, URI initiatorUri) {
URI storageUri = mask.getStorageDevice();
if (NullColumnValueGetter.isNullURI(storageUri)) {
return false;
}
List<ExportMask> results = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, ExportMask.class,
ContainmentConstraint.Factory.getConstraint(ExportMask.class, "initiators", initiatorUri));
for (ExportMask exportMask : results) {
if (exportMask != null && !exportMask.getId().equals(mask.getId()) && storageUri.equals(exportMask.getStorageDevice())) {
return true;
}
}
return false;
}
static public int getNumberOfExportGroupsWithVolume(Initiator initiator, URI blockObjectId, DbClient dbClient) {
List<ExportGroup> list = getInitiatorVolumeExportGroups(initiator, blockObjectId, dbClient);
return (list != null) ? list.size() : 0;
}
static public String computeResourceForInitiator(ExportGroup exportGroup, Initiator initiator) {
String value = NullColumnValueGetter.getNullURI().toString();
if (exportGroup.forCluster()) {
value = initiator.getClusterName();
} else if (exportGroup.forHost() || (exportGroup.forInitiator() && initiator.getHost() != null)) {
value = initiator.getHost().toString();
}
return value;
}
/**
* Using the ExportGroup object, produces a mapping of the BlockObject URI to LUN value
*
* @param dbClient
* @param storage
* @param exportGroup
* @return
*/
static public Map<URI, Integer> getExportGroupVolumeMap(DbClient dbClient, StorageSystem storage,
ExportGroup exportGroup) {
Map<URI, Integer> map = new HashMap<>();
if (exportGroup != null && exportGroup.getVolumes() != null) {
for (Map.Entry<String, String> entry : exportGroup.getVolumes().entrySet()) {
URI uri = URI.create(entry.getKey());
Integer lun = Integer.valueOf(entry.getValue());
BlockObject blockObject = BlockObject.fetch(dbClient, uri);
if (blockObject != null && blockObject.getStorageController().equals(storage.getId())) {
map.put(uri, lun);
}
}
}
return map;
}
/**
* Validate that an ExportGroup's mapping of Volume URIs to LUNs contains only one
* entry for each LUN. Otherwise, throw an Exception containing detailed information.
*
* @param exportGroupName the name of the ExportGroup containing this volume to URI mapping
* @param volumeMap a Map of Volume URI to LUN Integer for an ExportGroup
* @throws ControllerException if there are LUN inconsistencies
*/
static public void validateExportGroupVolumeMap(String exportGroupName, Map<URI, Integer> volumeMap) throws ControllerException {
// reverse the volume map to consist of LUN Integers to Volume URIs
Map<Integer, List<URI>> reversedVolumeMap = new HashMap<Integer, List<URI>>();
for (Entry<URI, Integer> entry : volumeMap.entrySet()) {
List<URI> uriList = reversedVolumeMap.get(entry.getValue());
if (null == uriList) {
uriList = new ArrayList<URI>();
reversedVolumeMap.put(entry.getValue(), uriList);
}
uriList.add(entry.getKey());
}
// filter out any LUN keys with just one entry (which are valid entries)
// also, -1 (LUN_UNASSIGNED) can be valid for more than one Volume, so skip that, too
List<String> validationErrors = new ArrayList<String>();
reversedVolumeMap.remove(ExportGroup.LUN_UNASSIGNED);
for (Entry<Integer, List<URI>> entry : reversedVolumeMap.entrySet()) {
// if there is more than one entry for the LUN, we have a problem
if (entry.getValue().size() > 1) {
String vols = Joiner.on(", ").join(entry.getValue());
String error = "LUN " + entry.getKey() + " is mapped to more than one volume (" + vols + ")";
_log.error("ExportGroup {} has LUN inconsistency: {}", exportGroupName, error);
validationErrors.add(error);
}
}
// throw an Exception if we encountered any LUN inconsistencies
if (!validationErrors.isEmpty()) {
String details = Joiner.on(". ").join(validationErrors);
throw DeviceControllerException.exceptions.exportGroupInconsistentLunViolation(exportGroupName, details);
}
_log.info("volume map for Export Group {} has valid LUN information: {}", exportGroupName, volumeMap);
}
/**
* This method updates zoning map to add new assignments.
*
* @param dbClient an instance of {@link DbClient}
* @param exportMask The reference to exportMask
* @param assignments New assignments Map of initiator to storagePorts that will be updated in the zoning map
* @param exportMasksToUpdateOnDeviceWithStoragePorts OUT param -- Map of ExportMask to new Storage ports
* @return returns an updated exportMask
*/
public static ExportMask updateZoningMap(DbClient dbClient, ExportMask exportMask, Map<URI, List<URI>> assignments,
Map<URI, List<URI>> exportMasksToUpdateOnDeviceWithStoragePorts) {
StringSetMap existingZoningMap = exportMask.getZoningMap();
for (URI initiatorURI : assignments.keySet()) {
boolean initiatorMatchFound = false;
if (existingZoningMap != null && !existingZoningMap.isEmpty()) {
for (String initiatorId : existingZoningMap.keySet()) {
if (initiatorURI.toString().equals(initiatorId)) {
StringSet ports = existingZoningMap.get(initiatorId);
if (ports != null && !ports.isEmpty()) {
initiatorMatchFound = true;
StringSet newTargets = StringSetUtil.uriListToStringSet(assignments.get(initiatorURI));
if (!ports.containsAll(newTargets)) {
ports.addAll(newTargets);
// Adds zoning map entry with new and existing ports. Its kind of updating storage ports for the initiator.
exportMask.addZoningMapEntry(initiatorId, ports);
updateExportMaskStoragePortsMap(exportMask, exportMasksToUpdateOnDeviceWithStoragePorts,
assignments, initiatorURI);
}
}
}
}
}
if (!initiatorMatchFound) {
// Adds new zoning map entry for the initiator with new assignments as there isn't one already.
exportMask.addZoningMapEntry(initiatorURI.toString(), StringSetUtil.uriListToStringSet(assignments.get(initiatorURI)));
updateExportMaskStoragePortsMap(exportMask, exportMasksToUpdateOnDeviceWithStoragePorts,
assignments, initiatorURI);
}
}
dbClient.persistObject(exportMask);
return exportMask;
}
/**
* This method just updates the passed in exportMasksToUpdateOnDeviceWithStoragePorts map with
* the new storage ports assigned for the initiator for a exportMask.
*
* @param exportMask The reference to exportMask
* @param exportMasksToUpdateOnDeviceWithStoragePorts OUT param -- map of exportMask to update with new storage ports
* @param assignments New assignments Map of initiator to storage ports
* @param initiatorURI The initiator URI for which storage ports are updated in the exportMask
*/
private static void updateExportMaskStoragePortsMap(ExportMask exportMask,
Map<URI, List<URI>> exportMasksToUpdateOnDeviceWithStoragePorts,
Map<URI, List<URI>> assignments, URI initiatorURI) {
if (exportMasksToUpdateOnDeviceWithStoragePorts.get(exportMask.getId()) != null) {
exportMasksToUpdateOnDeviceWithStoragePorts.get(exportMask.getId()).addAll(assignments.get(initiatorURI));
} else {
exportMasksToUpdateOnDeviceWithStoragePorts.put(exportMask.getId(), assignments.get(initiatorURI));
}
}
/**
* Take in a list of storage port names (hex digits separated by colons),
* then returns a list of URIs representing the StoragePort URIs they represent.
*
* This method ignores the storage ports from cinder storage systems.
*
* @param storagePorts [in] - Storage port name, hex digits separated by colons
* @return List of StoragePort URIs
*/
public static List<String> storagePortNamesToURIs(DbClient dbClient,
List<String> storagePorts) {
List<String> storagePortURIStrings = new ArrayList<String>();
Map<URI, String> systemURIToType = new HashMap<URI, String>();
for (String port : storagePorts) {
URIQueryResultList portUriList = new URIQueryResultList();
dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getStoragePortEndpointConstraint(port), portUriList);
Iterator<URI> storagePortIter = portUriList.iterator();
while (storagePortIter.hasNext()) {
URI portURI = storagePortIter.next();
StoragePort sPort = dbClient.queryObject(StoragePort.class, portURI);
if (sPort != null && !sPort.getInactive()) {
String systemType = getStoragePortSystemType(dbClient, sPort, systemURIToType);
// ignore cinder managed storage system's port
if (!DiscoveredDataObject.Type.openstack.name().equals(systemType)) {
storagePortURIStrings.add(portURI.toString());
}
}
}
}
return storagePortURIStrings;
}
private static String getStoragePortSystemType(DbClient dbClient,
StoragePort port, Map<URI, String> systemURIToType) {
URI systemURI = port.getStorageDevice();
String systemType = systemURIToType.get(systemURI);
if (systemType == null) {
StorageSystem system = dbClient.queryObject(StorageSystem.class, systemURI);
systemType = system.getSystemType();
systemURIToType.put(systemURI, systemType);
}
return systemType;
}
/**
* Checks to see if the export group is for RecoverPoint
*
* @param exportGroup
* The export group to check
* @return True if this export group is for RecoverPoint, false otherwise.
*/
public static boolean checkIfExportGroupIsRP(ExportGroup exportGroup) {
if (exportGroup == null) {
return false;
}
return exportGroup.checkInternalFlags(Flag.RECOVERPOINT);
}
/**
* Checks to see if the initiators passed in are for RecoverPoint.
*
* Convenience method to load the actual Initiators from the StringSet first
* before calling checkIfInitiatorsForRP(List<Initiator> initiatorList).
*
* @param dbClient
* DB Client
* @param initiatorList
* The StringSet of initiator IDs to check
* @return True if there are RecoverPoint Initiators in the passed in list,
* false otherwise
*/
public static boolean checkIfInitiatorsForRP(DbClient dbClient, StringSet initiatorList) {
if (dbClient == null || initiatorList == null) {
return false;
}
List<Initiator> initiators = new ArrayList<Initiator>();
for (String initiatorId : initiatorList) {
Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(initiatorId));
if (initiator != null) {
initiators.add(initiator);
}
}
return checkIfInitiatorsForRP(initiators);
}
/**
* Check the list of passed in initiators and check if the RP flag is set.
*
* @param initiatorList
* List of Initiators
* @return True if there are RecoverPoint Initiators in the passed in list,
* false otherwise
*/
public static boolean checkIfInitiatorsForRP(List<Initiator> initiatorList) {
if (initiatorList == null) {
return false;
}
_log.debug("Checking Initiators to see if this is RP");
boolean isRP = true;
for (Initiator initiator : initiatorList) {
if (!initiator.checkInternalFlags(Flag.RECOVERPOINT)) {
isRP = false;
break;
}
}
_log.debug("Are these RP initiators? " + (isRP ? "Yes!" : "No!"));
return isRP;
}
/**
* Figure out whether or not we need to use the EMC Force flag for the SMIS
* operation being performed on this volume.
*
* @param _dbClient
* DB Client
* @param blockObjectURI
* BlockObject to check
* @return Whether or not to use the EMC force flag
*/
public static boolean useEMCForceFlag(DbClient _dbClient, URI blockObjectURI) {
boolean forceFlag = false;
// If there are any volumes that are RP, then we need to use the force flag on this operation
BlockObject bo = Volume.fetchExportMaskBlockObject(_dbClient, blockObjectURI);
if (bo != null && BlockObject.checkForRP(_dbClient, bo.getId())) {
// Set the force flag if the Block object is an RP Volume or BlockSnapshot.
forceFlag = true;
}
return forceFlag;
}
/**
* Get the varrays used for the set of volumes for a storage system.
* For the VPlex, it will include the HA virtual array if there are distributed volumes.
*
* @param exportGroup -- ExportGroup instance
* @param storageURI -- the URI of the Storage System
* @param dbClient
*/
public static List<URI> getVarraysForStorageSystemVolumes(ExportGroup exportGroup, URI storageURI, DbClient dbClient) {
List<URI> varrayURIs = new ArrayList<URI>();
varrayURIs.add(exportGroup.getVirtualArray());
Map<URI, Map<URI, Integer>> systemToVolumeMap = getStorageToVolumeMap(exportGroup, false, dbClient);
if (systemToVolumeMap.containsKey(storageURI)) {
Set<URI> blockObjectURIs = systemToVolumeMap.get(storageURI).keySet();
for (URI blockObjectURI : blockObjectURIs) {
BlockObject blockObject = BlockObject.fetch(dbClient, blockObjectURI);
Set<URI> blockObjectVarrays = getBlockObjectVarrays(blockObject, dbClient);
varrayURIs.addAll(blockObjectVarrays);
}
}
return varrayURIs;
}
/**
* Given an exportGroup, generate a map of Storage System URI to a map of BlockObject URI to lun id
* for BlockObjects in the Export Group.
*
* @param exportGroup
* @param protection
* @param dbClient
* @return Map of Storage System URI to map of BlockObject URI to lun id Integer
*/
public static Map<URI, Map<URI, Integer>> getStorageToVolumeMap(ExportGroup exportGroup, boolean protection, DbClient dbClient) {
Map<URI, Map<URI, Integer>> map = new HashMap<URI, Map<URI, Integer>>();
StringMap volumes = exportGroup.getVolumes();
if (volumes == null) {
return map;
}
for (String uriString : volumes.keySet()) {
URI blockURI = URI.create(uriString);
BlockObject block = BlockObject.fetch(dbClient, blockURI);
// If this is an RP-based Block Snapshot, use the protection controller instead of the underlying block controller
URI storage = (block.getProtectionController() != null && protection && block.getId().toString().contains("BlockSnapshot"))
? block.getProtectionController() : block.getStorageController();
if (map.get(storage) == null) {
map.put(storage, new HashMap<URI, Integer>());
}
map.get(storage).put(blockURI, Integer.valueOf(volumes.get(uriString)));
}
return map;
}
/**
* Get the possible Varrays a BlockObject can be associated with.
* Handles the Vplex... which can be the BlockObject's varray,
* or the HA Virtual array in the Vpool.
*
* @param blockObject
* @param dbClient
* @return Set<URI> of Varray URIs
*/
public static Set<URI> getBlockObjectVarrays(BlockObject blockObject, DbClient dbClient) {
Set<URI> varrayURIs = new HashSet<URI>();
varrayURIs.add(blockObject.getVirtualArray());
VirtualPool vpool = getBlockObjectVirtualPool(blockObject, dbClient);
if (vpool != null) {
if (vpool.getHaVarrayVpoolMap() != null) {
for (String varrayId : vpool.getHaVarrayVpoolMap().keySet()) {
URI varrayURI = URI.create(varrayId);
if (!varrayURIs.contains(varrayURI)) {
varrayURIs.add(varrayURI);
}
}
}
}
return varrayURIs;
}
/**
* Get the Virtual Pool for a Block Object.
*
* @param blockObject
* @param dbClient
* @return VirtualPool or null if could not locate
*/
public static VirtualPool getBlockObjectVirtualPool(BlockObject blockObject, DbClient dbClient) {
Volume volume = null;
if (blockObject instanceof BlockSnapshot) {
BlockSnapshot snapshot = (BlockSnapshot) blockObject;
volume = dbClient.queryObject(Volume.class, snapshot.getParent());
} else if (blockObject instanceof Volume) {
volume = (Volume) blockObject;
}
if (volume != null) {
VirtualPool vpool = dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
return vpool;
}
return null;
}
/**
* Filters Initiators for non-VPLEX systems by the ExportGroup varray.
* Initiators not in the Varray are removed from the newInitiators list.
*
* @param exportGroup -- ExportGroup used to get virtual array.
* @param newInitiators -- List of new initiators to be processed
* @param storageURI -- storage system URI
* @param dbClient -- DbClient for database
* @return filteredInitiators -- New list of filtered initiators
*/
public static List<URI> filterNonVplexInitiatorsByExportGroupVarray(
ExportGroup exportGroup, List<URI> newInitiators, URI storageURI, DbClient dbClient) {
List<URI> filteredInitiators = new ArrayList<URI>(newInitiators);
StorageSystem storageSystem = dbClient.queryObject(StorageSystem.class, storageURI);
// in the case of RecoverPoint, storageURI refers to a ProtectionSystem so storageSystem will be null
if (storageSystem != null && !storageSystem.getSystemType().equals(DiscoveredDataObject.Type.vplex.name())) {
filterOutInitiatorsNotAssociatedWithVArray(exportGroup.getVirtualArray(), filteredInitiators, dbClient);
}
return filteredInitiators;
}
/**
* Routine will examine the 'newInitiators' list and remove any that do not have any association
* to the VirtualArray.
*
* @param virtualArrayURI [in] - VirtualArray URI reference
* @param newInitiators [in/out] - List of initiator URIs to examine.
* @param dbClient [in] -- Used to access database
*/
public static void filterOutInitiatorsNotAssociatedWithVArray(URI virtualArrayURI,
List<URI> newInitiators, DbClient dbClient) {
Iterator<URI> it = newInitiators.iterator();
while (it.hasNext()) {
URI uri = it.next();
Initiator initiator = dbClient.queryObject(Initiator.class, uri);
if (initiator == null) {
_log.info(String.format("Initiator %s was not found in DB. Will be eliminated from request payload.",
uri.toString()));
it.remove();
continue;
}
if (!isInitiatorInVArraysNetworks(virtualArrayURI, initiator, dbClient)) {
_log.info(String.format("Initiator %s (%s) will be eliminated from the payload " +
"because it was not associated with Virtual Array %s",
initiator.getInitiatorPort(), initiator.getId().toString(),
virtualArrayURI.toString()));
it.remove();
}
}
}
/**
* Validate if the initiator is linked to the VirtualArray through some Network
*
* @param virtualArrayURI [in] - VirtualArray URI reference
* @param initiator [in] - the initiator
* @return true iff the initiator belongs to a Network and that Network has the VirtualArray
*/
public static boolean isInitiatorInVArraysNetworks(URI virtualArrayURI, Initiator initiator, DbClient dbClient) {
boolean foundAnAssociatedNetwork = false;
Set<NetworkLite> networks = NetworkUtil.getEndpointAllNetworksLite(initiator.getInitiatorPort(), dbClient);
if (networks == null || networks.isEmpty()) {
// No network associated with the initiator, so it should be removed from the list
_log.info(String.format("Initiator %s (%s) is not associated with any network.",
initiator.getInitiatorPort(), initiator.getId().toString()));
return false;
} else {
// Search through the networks determining if the any are associated with ExportGroup's VirtualArray.
for (NetworkLite networkLite : networks) {
if (networkLite == null) {
continue;
}
Set<String> varraySet = networkLite.fetchAllVirtualArrays();
if (varraySet != null && varraySet.contains(virtualArrayURI.toString())) {
_log.info(String.format("Initiator %s (%s) was found to be associated to VirtualArray %s through network %s.",
initiator.getInitiatorPort(), initiator.getId().toString(), virtualArrayURI.toString(),
networkLite.getNativeGuid()));
foundAnAssociatedNetwork = true;
// Though we could break this loop here, let's continue the loop so that
// we can log what other networks that the initiator is seen in
}
}
}
return foundAnAssociatedNetwork;
}
/**
* Check if any ExportGroups passed in contain the initiator
*
* @param dbClient [in] - DB client object
* @param exportGroupURIs [in] - List of ExportGroup URIs referencing ExportGroups to check
* @param initiator [in] - The initiator check
* @return true if any of the ExportGroups referenced in the exportGroupURIs list has the initiator
*/
public static boolean checkIfAnyExportGroupsContainInitiator(DbClient dbClient, Set<URI> exportGroupURIs, Initiator initiator) {
Iterator<ExportGroup> exportGroupIterator = dbClient.queryIterativeObjects(ExportGroup.class, exportGroupURIs, true);
while (exportGroupIterator.hasNext()) {
ExportGroup exportGroup = exportGroupIterator.next();
if (exportGroup.hasInitiator(initiator)) {
return true;
}
}
return false;
}
/**
* Check if any ExportGroups passed in contain the initiator and block object
*
* @param dbClient [in] - DB client object
* @param exportGroupURIs [in] - List of ExportGroup URIs referencing ExportGroups to check
* @param initiator [in] - The initiator check
* @param blockObject [in] - The block object
* @return true if any of the ExportGroups referenced in the exportGroupURIs list has the initiator
*/
public static boolean checkIfAnyExportGroupsContainInitiatorAndBlockObject(
DbClient dbClient, Set<URI> exportGroupURIs, Initiator initiator,
BlockObject blockObject) {
Iterator<ExportGroup> exportGroupIterator = dbClient.queryIterativeObjects(ExportGroup.class, exportGroupURIs, true);
while (exportGroupIterator.hasNext()) {
ExportGroup exportGroup = exportGroupIterator.next();
if (exportGroup.hasInitiator(initiator) && exportGroup.hasBlockObject(blockObject.getId())) {
return true;
}
}
return false;
}
/**
* Returns the list of export groups referencing the mask
*
* @param uri the export mask UTI
* @param dbClient and instance of {@link DbClient}
* @return the list of export groups referencing the mask
*/
public static List<ExportGroup> getExportGroupsForMask(URI uri, DbClient dbClient) {
URIQueryResultList exportGroupUris = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getExportMaskExportGroupConstraint(uri), exportGroupUris);
return DataObjectUtils.iteratorToList(dbClient.queryIterativeObjects(ExportGroup.class,
DataObjectUtils.iteratorToList(exportGroupUris)));
}
/**
* Find out if the mirror is part of any export group/export mask.
* If yes, remove the mirror and add the promoted volume.
*
* @param mirror
* @param promotedVolume
* @param dbClient
*/
public static void updatePromotedMirrorExports(BlockMirror mirror, Volume promotedVolume, DbClient dbClient) {
URIQueryResultList egUris = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(mirror.getId()), egUris);
List<ExportGroup> exportGroups = dbClient.queryObject(ExportGroup.class, egUris);
Set<ExportMask> mirrorExportMasks = new HashSet<ExportMask>();
List<DataObject> updatedObjects = new ArrayList<DataObject>();
for (ExportGroup exportGroup : exportGroups) {
if (exportGroup != null && !exportGroup.getInactive() && exportGroup.getExportMasks() != null) {
List<URI> exportMasks = new ArrayList<URI>(Collections2.transform(
exportGroup.getExportMasks(), CommonTransformerFunctions.FCTN_STRING_TO_URI));
mirrorExportMasks.addAll(dbClient.queryObject(ExportMask.class, exportMasks));
// remove the mirror from export group and add the promoted volume
String lunString = exportGroup.getVolumes().get(mirror.getId().toString());
_log.info("Removing mirror {} from export group {}", mirror.getId(), exportGroup.getId());
exportGroup.removeVolume(mirror.getId());
_log.info("Adding promoted volume {} to export group {}", promotedVolume.getId(), exportGroup.getId());
exportGroup.getVolumes().put(promotedVolume.getId().toString(), lunString);
updatedObjects.add(exportGroup);
}
}
for (ExportMask exportMask : mirrorExportMasks) {
if (!exportMask.getInactive()
&& exportMask.getStorageDevice().equals(mirror.getStorageController())
&& exportMask.hasVolume(mirror.getId())
&& exportMask.getInitiators() != null && exportMask.getStoragePorts() != null) {
String lunString = exportMask.getVolumes().get(mirror.getId().toString());
_log.info("Removing mirror {} from export mask {}", mirror.getId(), exportMask.getId());
exportMask.removeVolume(mirror.getId());
exportMask.removeFromUserCreatedVolumes(mirror);
_log.info("Adding promoted volume {} to export mask {}", promotedVolume.getId(), exportMask.getId());
exportMask.addToUserCreatedVolumes(promotedVolume);
exportMask.getVolumes().put(promotedVolume.getId().toString(), lunString);
updatedObjects.add(exportMask);
}
}
dbClient.updateObject(updatedObjects);
}
/**
* Find all the ports in a storage system that can be assigned in a given virtual array. These are
* registered ports that are assigned to the virtual array, in good discovery and operational status.
*
* @param dbClient an instance of {@link DbClient}
* @param storageSystemURI the URI of the storage system
* @param varrayURI the virtual array
* @param pathParams ExportPathParams may contain a set of allowable ports. Optional, can be null.
* @return a list of storage ports that are in good operational status and assigned to the virtual array
*/
public static List<StoragePort> getStorageSystemAssignablePorts(DbClient dbClient, URI storageSystemURI,
URI varrayURI, ExportPathParams pathParams) {
URIQueryResultList sports = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getStorageDeviceStoragePortConstraint(storageSystemURI), sports);
Iterator<URI> it = sports.iterator();
List<StoragePort> spList = new ArrayList<StoragePort>();
List<String> notRegisteredOrOk = new ArrayList<String>();
List<String> notInVarray = new ArrayList<String>();
List<String> notInPathParams = new ArrayList<String>();
while (it.hasNext()) {
StoragePort sp = dbClient.queryObject(StoragePort.class, it.next());
if (sp.getInactive() || sp.getNetwork() == null
|| !DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name().equals(sp.getCompatibilityStatus())
|| !DiscoveryStatus.VISIBLE.name().equals(sp.getDiscoveryStatus())
|| !sp.getRegistrationStatus().equals(StoragePort.RegistrationStatus.REGISTERED.name())
|| StoragePort.OperationalStatus.NOT_OK.equals(StoragePort.OperationalStatus.valueOf(sp.getOperationalStatus()))
|| StoragePort.PortType.valueOf(sp.getPortType()) != StoragePort.PortType.frontend) {
_log.debug(
"Storage port {} is not selected because it is inactive, is not compatible, is not visible, has no network assignment, "
+
"is not registered, has a status other than OK, or is not a frontend port",
sp.getLabel());
notRegisteredOrOk.add(sp.qualifiedPortName());
} else if (sp.getTaggedVirtualArrays() == null || !sp.getTaggedVirtualArrays().contains(varrayURI.toString())) {
_log.debug("Storage port {} not selected because it is not connected " +
"or assigned to requested virtual array {}", sp.getNativeGuid(), varrayURI);
notInVarray.add(sp.qualifiedPortName());
} else if (pathParams != null && !pathParams.getStoragePorts().isEmpty()
&& !pathParams.getStoragePorts().contains(sp.getId().toString())) {
_log.debug("Storage port {} not selected because it is not in ExportPathParams port list", sp.getNativeGuid());
notInPathParams.add(sp.qualifiedPortName());
} else {
spList.add(sp);
}
}
if (!notRegisteredOrOk.isEmpty()) {
_log.info("Ports not selected because they are inactive, have no network assignment, " +
"are not registered, bad operational status, or not type front-end: "
+ Joiner.on(" ").join(notRegisteredOrOk));
}
if (!notInVarray.isEmpty()) {
_log.info("Ports not selected because they are not assigned to the requested virtual array: "
+ varrayURI + " " + Joiner.on(" ").join(notInVarray));
}
if (!notInPathParams.isEmpty()) {
_log.info("Ports not selected because they are not in the ExportPathParams port list: "
+ Joiner.on(" ").join(notInPathParams));
}
return spList;
}
/**
* Given a list of storage ports and networks, map the ports to the networks. If the port network
* is in the networks collection, the port is mapped to it. If the port network is not in the
* networks collection but can is routed to it, then the port is mapped to the routed network.
*
* @param ports the ports to be mapped to their networks
* @param networks the networks
* @param _dbClient and instance of DbClient
* @return a map of networks and ports that can be used by initiators in the network.
*/
public static Map<NetworkLite, List<StoragePort>> mapStoragePortsToNetworks(Collection<StoragePort> ports,
Collection<NetworkLite> networks, DbClient _dbClient) {
Map<NetworkLite, List<StoragePort>> localPorts = new HashMap<NetworkLite, List<StoragePort>>();
Map<NetworkLite, List<StoragePort>> remotePorts = new HashMap<NetworkLite, List<StoragePort>>();
for (NetworkLite network : networks) {
for (StoragePort port : ports) {
if (port.getNetwork().equals(network.getId())) {
StringMapUtil.addToListMap(localPorts, network, port);
} else if (network.hasRoutedNetworks(port.getNetwork())) {
StringMapUtil.addToListMap(remotePorts, network, port);
}
}
}
// consolidate local and remote ports
for (NetworkLite network : networks) {
if (localPorts.get(network) == null && remotePorts.get(network) != null) {
localPorts.put(network, remotePorts.get(network));
}
}
return localPorts;
}
/**
* Consolidate the assignments made from pre-zoned ports with those made by ordinary port assignment.
* existingAndPrezonedZoningMap contains all pre-existing assignments plus those made from pre-zoned
* ports. exportMask.zoningMap contains all pre-existing assignments, and 'assignments' has all ports
* made by ordinary assignment.
*
* The function consolidate the targets to be added to the masking view by adding those taken from
* pre-zoned ports to those taken from the other set of ports. The way ports taken from pre-zoned
* ports are identified is by comparing existingAndPrezonedZoningMap to exportMask.zoningMap, these
* are ports assigned to initiators found in existingAndPrezonedZoningMap but not in exportMask.zoningMap.
* This is because the port assignment never adds new ports to already used initiators.
*
* @param exportMaskZoningMap -- the export mask zoningMap before any assignments are made
* @param assignments -- assignments made from all ports not based on what is pre-zoned.
* @param existingAndPrezonedZoningMap -- assignments made from pre-zoned ports plus all pre-existing assignments.
*/
public static void addPrezonedAssignments(StringSetMap exportMaskZoningMap, Map<URI, List<URI>> assignments,
StringSetMap existingAndPrezonedZoningMap) {
for (String iniUriStr : existingAndPrezonedZoningMap.keySet()) {
StringSet iniPorts = new StringSet(existingAndPrezonedZoningMap.get(iniUriStr));
if (exportMaskZoningMap != null) {
if (exportMaskZoningMap.containsKey(iniUriStr)) {
iniPorts.removeAll(exportMaskZoningMap.get(iniUriStr));
}
}
if (!iniPorts.isEmpty()) {
URI iniUri = URI.create(iniUriStr);
if (!assignments.containsKey(iniUri)) {
assignments.put(iniUri, new ArrayList<URI>());
}
assignments.get(iniUri).addAll(StringSetUtil.stringSetToUriList(iniPorts));
}
}
}
/**
* Gets the ExportGroup to be used for VPlex when reusing an ExportMask.
* Will find the ExportGroup containing the mask, or it will create a new one if necessary.
*
* @param vplex - VPlex StorageSystem
* @param array - Backend StorageSystem
* @param virtualArrayURI - VirtualArray to which the VPlex and backend StorageSystem apply
* @param mask - ExportMask that is being reused
* @param initiators - Collection<Initiator> VPLEX initiators to array
* @param tenantURI - Tenant URI
* @param projectURI - Project URI
* @return ExportGroup that is applicable for the Vplex and backend array
*/
public static ExportGroup getVPlexExportGroup(DbClient dbClient, StorageSystem vplex, StorageSystem array, URI virtualArrayURI,
ExportMask mask, Collection<Initiator> initiators, URI tenantURI, URI projectURI) {
// Determine all the possible existing Export Groups
Map<String, ExportGroup> possibleExportGroups = new HashMap<String, ExportGroup>();
for (Initiator initiator : initiators) {
List<ExportGroup> groups = ExportUtils.getInitiatorExportGroups(initiator, dbClient);
for (ExportGroup group : groups) {
if (!possibleExportGroups.containsKey(group.getId().toString())) {
possibleExportGroups.put(group.getId().toString(), group);
}
}
}
// If there are possible Export Groups, look for one with this mask.
for (ExportGroup group : possibleExportGroups.values()) {
if (group.hasMask(mask.getId())) {
_log.info(String.format("Returning ExportGroup %s", group.getLabel()));
return group;
}
}
return createVplexExportGroup(dbClient, vplex, array, initiators, virtualArrayURI, projectURI, tenantURI, 0, mask);
}
/**
* Create an ExportGroup.
*
* @param vplex -- VPLEX StorageSystem
* @param array -- Array StorageSystem
* @param initiators -- Collection<Initiator> representing VPLEX back-end ports.
* @param virtualArrayURI
* @param projectURI
* @param tenantURI
* @param numPaths Value of maxPaths to be put in ExportGroup
* @param exportMask IFF non-null, will add the exportMask to the Export Group.
* @return newly created ExportGroup persisted in DB.
*/
public static ExportGroup createVplexExportGroup(DbClient dbClient, StorageSystem vplex, StorageSystem array,
Collection<Initiator> initiators, URI virtualArrayURI, URI projectURI, URI tenantURI, int numPaths, ExportMask exportMask) {
String groupName = getExportGroupName(vplex, array)
+ "_" + UUID.randomUUID().toString().substring(28);
if (exportMask != null) {
String arrayName = array.getSystemType().replace("block", "")
+ array.getSerialNumber().substring(array.getSerialNumber().length() - 4);
groupName = exportMask.getMaskName() + "_" + arrayName;
}
// No existing group has the mask, let's create one.
ExportGroup exportGroup = new ExportGroup();
exportGroup.setId(URIUtil.createId(ExportGroup.class));
exportGroup.setLabel(groupName);
exportGroup.setProject(new NamedURI(projectURI, exportGroup.getLabel()));
exportGroup.setVirtualArray(vplex.getVirtualArray());
exportGroup.setTenant(new NamedURI(tenantURI, exportGroup.getLabel()));
exportGroup.setGeneratedName(groupName);
exportGroup.setVolumes(new StringMap());
exportGroup.setOpStatus(new OpStatusMap());
exportGroup.setVirtualArray(virtualArrayURI);
exportGroup.setNumPaths(numPaths);
// Add the initiators into the ExportGroup.
for (Initiator initiator : initiators) {
exportGroup.addInitiator(initiator);
}
// If we have an Export Mask, add it into the Export Group.
if (exportMask != null) {
exportGroup.addExportMask(exportMask.getId());
}
// Persist the ExportGroup
dbClient.createObject(exportGroup);
_log.info(String.format("Returning new ExportGroup %s", exportGroup.getLabel()));
return exportGroup;
}
/**
* Returns the ExportGroup name to be used between a particular VPlex and underlying Storage Array.
* It is based on the serial numbers of the Vplex and Array. Therefore the same ExportGroup name
* will always be used, and it always starts with "VPlex".
*
* @param vplex [IN] - VPlex StorageArray
* @param array [IN] - Backend StorageArray
* @return String that represents the unique combination of Vplex-to-backend array
*/
public static String getExportGroupName(StorageSystem vplex, StorageSystem array) {
// Unfortunately, using the full VPlex serial number with the Array serial number
// proves to be quite lengthy! We can run into issues on SMIS where
// max length (represented as STOR_DEV_GROUP_MAX_LEN) is 64 characters.
// Not to mention, other steps append to this name too.
// So lets chop everything but the last 4 digits from both serial numbers.
// This should be unique enough.
int endIndex = vplex.getSerialNumber().length();
int beginIndex = endIndex - 4;
String modfiedVPlexSerialNumber = vplex.getSerialNumber().substring(beginIndex, endIndex);
endIndex = array.getSerialNumber().length();
beginIndex = endIndex - 4;
String modfiedArraySerialNumber = array.getSerialNumber().substring(beginIndex, endIndex);
return String.format("VPlex_%s_%s", modfiedVPlexSerialNumber, modfiedArraySerialNumber);
}
/**
* Given an updatedBlockObjectMap (maps BlockObject URI to Lun Integer) representing the desired state,
* and an Export Group, makes addedBlockObjects containing the entries that were added,
* and removedBlockObjects containing the entries that were removed.
*
* @param updatedBlockObjectMap : desired state of the Block Object Map
* @param exportGroup : existing map taken from exportGroup.getVolumes()
* @param addedBlockObjects : OUTPUT - contains map of added Block Objects
* @param removedBlockObjects : OUTPUT -- contains map of removed Block Objects
*/
public static void getAddedAndRemovedBlockObjects(Map<URI, Integer> updatedBlockObjectMap,
ExportGroup exportGroup, Map<URI, Integer> addedBlockObjects, Map<URI, Integer> removedBlockObjects) {
Map<URI, Integer> existingBlockObjectMap = StringMapUtil.stringMapToVolumeMap(exportGroup.getVolumes());
// Determine the removed entries; they are in existing but not updated
for (Entry<URI, Integer> entry : existingBlockObjectMap.entrySet()) {
if (!updatedBlockObjectMap.keySet().contains(entry.getKey())) {
removedBlockObjects.put(entry.getKey(), entry.getValue());
}
}
// Determine the added entries; they are in updated but not existing
for (Entry<URI, Integer> entry : updatedBlockObjectMap.entrySet()) {
if (!existingBlockObjectMap.keySet().contains(entry.getKey())) {
addedBlockObjects.put(entry.getKey(), entry.getValue());
}
}
}
/**
* This routine will examine the ExportGroup and ExportMask and attempt to reconcile its HLUs.
* This would include volumes in 'volumeMap' and any that appear to not have their HLUs filled in.
* For this routine we only care about ExportMasks that were created by the system.
*
* NOTE: ExportGroup is not persisted here.
*
* @param dbClient [IN] - DbClient for DB access
* @param exportGroup [IN] - ExportGroup to update with HLUs
* @param exportMask [IN] - ExportMask that we updated for this add volumes request
*/
public static void reconcileHLUs(DbClient dbClient, ExportGroup exportGroup, ExportMask exportMask, Map<URI, Integer> volumeMap) {
// We should only care to do this when there are system created ExportMasks that have volumes
if (exportMask.getCreatedBySystem() && exportMask.getVolumes() != null) {
// CTRL-11544: Set the hlu in the export group too
for (URI boURI : volumeMap.keySet()) {
String hlu = exportMask.returnVolumeHLU(boURI);
_log.info(String.format("ExportGroup %s (%s) update volume HLU: %s -> %s", exportGroup.getLabel(), exportGroup.getId(),
boURI, hlu));
exportGroup.addVolume(boURI, Integer.parseInt(hlu));
}
reconcileExportGroupsHLUs(dbClient, exportGroup);
}
}
/**
* Examine ExportGroup's volumes to find any that do not have their HLU filled in. In case it is not filled, the ExportMasks
* will be searched to find an HLU to assign for the volume.
*
* NOTE: ExportGroup is not persisted here.
*
* @param dbClient [IN] - DbClient for DB access
* @param exportGroup [IN] - ExportGroup to examine volumes
*/
public static void reconcileExportGroupsHLUs(DbClient dbClient, ExportGroup exportGroup) {
// Find the volumes that don't have their HLU filled in ...
List<String> egVolumesWithoutHLUs = findVolumesWithoutHLUs(exportGroup);
if (!egVolumesWithoutHLUs.isEmpty()) {
// There are volumes in the ExportGroup that don't have their HLUs filled in.
// Search through each ExportMask associated with the ExportGroup ...
for (ExportMask thisMask : ExportMaskUtils.getExportMasks(dbClient, exportGroup)) {
Iterator<String> volumeIter = egVolumesWithoutHLUs.iterator();
while (volumeIter.hasNext()) {
URI volumeURI = URI.create(volumeIter.next());
if (thisMask.hasVolume(volumeURI)) {
// This ExportMask has the volume we're interested in.
String hlu = thisMask.returnVolumeHLU(volumeURI);
// Let's apply its HLU if it's not the 'Unassigned' value ...
if (!hlu.equals(ExportGroup.LUN_UNASSIGNED_DECIMAL_STR)) {
_log.info(String.format("ExportGroup %s (%s) update volume HLU: %s -> %s", exportGroup.getLabel(),
exportGroup.getId(), volumeURI, hlu));
exportGroup.addVolume(volumeURI, Integer.valueOf(hlu));
// Now that we've found an HLU for this volume, there's no need to search for it in other ExportMasks.
// Let's remove it from the array list.
volumeIter.remove();
}
}
}
}
}
}
/**
* Return a list of Volume URI Strings that have ExportGroup.LUN_UNASSIGNED_DECIMAL_STR as their HLU
*
* @param exportGroup [IN] - ExportGroup to check
*
* @return List or Volume URI Strings
*/
public static List<String> findVolumesWithoutHLUs(ExportGroup exportGroup) {
List<String> result = new ArrayList<>();
if (exportGroup.getVolumes() != null) {
for (Map.Entry<String, String> entry : exportGroup.getVolumes().entrySet()) {
String volumeURIStr = entry.getKey();
String hlu = entry.getValue();
if (hlu.equals(ExportGroup.LUN_UNASSIGNED_DECIMAL_STR)) {
result.add(volumeURIStr);
}
}
}
return result;
}
/**
* Calculate free HLUs to use based on already assigned ones and the maximum number allowed.
*
* @param usedHlus the used hlus
* @param maxHLU the max hlu
* @return the free HLUs to use
*/
public static Set<Integer> calculateFreeHLUs(Set<Integer> usedHlus, Integer maxHLU) {
Set<Integer> freeHLUs = new LinkedHashSet<>();
// For max limit of 4096, 0 to 4095 can be assigned.
// Since it is cluster export (shared), the number can start from 1 since 0 will be used for boot lun.
for (int i = 1; i < maxHLU; i++) {
if (!usedHlus.contains(i)) {
freeHLUs.add(i);
}
}
_log.debug("free HLUs: {}", freeHLUs);
return freeHLUs;
}
/**
* Gets the maximum allowed HLU number for the storage array.
*
* @param storage the storage system
* @return the maximum allowed HLU number for the storage array
*/
public static Integer getMaximumAllowedHLU(StorageSystem storage) {
String systemType = storage.getSystemType();
String maxAllowedHLUKey = String.format(MAX_ALLOWED_HLU_KEY, systemType);
// Get and return max allowed HLU from co-ordinator.
String maxHLU = ControllerUtils.getPropertyValueFromCoordinator(coordinator, maxAllowedHLUKey);
_log.info("Maximum allowed HLU for system type {}: {}", systemType, maxHLU);
if (maxHLU == null || maxHLU.isEmpty()) {
throw DeviceControllerException.exceptions.volumeExportMaximumHluNotAvailable(systemType);
}
return Integer.parseInt(maxHLU);
}
/**
* Update free HLUs in the volume map.
*
* @param volumeMap the volume map
* @param freeHLUs the free hlus
*/
public static void updateFreeHLUsInVolumeMap(Map<URI, Integer> volumeMap, Set<Integer> freeHLUs) {
Iterator<Integer> freeHLUItr = freeHLUs.iterator();
for (Entry<URI, Integer> entry : volumeMap.entrySet()) {
if (freeHLUItr.hasNext()) {
entry.setValue(freeHLUItr.next());
} else {
String detailMsg = String.format("Requested volumes: {%s}, free HLUs available: {%s}",
Joiner.on(',').join(volumeMap.keySet()), freeHLUs);
_log.warn("No more free HLU available on array to assign. {}", detailMsg);
throw DeviceControllerException.exceptions.volumeExportReachedMaximumHlu(detailMsg);
}
}
_log.info("updated volume-HLU map: {}", volumeMap);
}
/**
* Gets all hosts initiators for the given cluster.
*
* @param clusterURI the cluster uri
* @return the cluster initiators
*/
public static List<URI> getAllInitiatorsForCluster(URI clusterURI, DbClient dbClient) {
List<URI> clusterInitaitors = new ArrayList<URI>();
List<Host> hosts = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, Host.class,
ContainmentConstraint.Factory.getContainedObjectsConstraint(clusterURI, Host.class, "cluster"));
for (Host host : hosts) {
List<Initiator> initiators = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, Initiator.class,
ContainmentConstraint.Factory.getContainedObjectsConstraint(host.getId(), Initiator.class, "host"));
for (Initiator initiator : initiators) {
clusterInitaitors.add(initiator.getId());
}
}
return clusterInitaitors;
}
/**
* Returns a list of ExportGroups that reference the given ExportMask,
* minus the given ExportGroup
*
* @param exportGroup
* the ExportGroup to exclude
* @param exportMask
* the ExportMask to locate in other ExportGroups
* @return a list of other ExportGroups containing the ExportMask
*/
public static List<ExportGroup> getOtherExportGroups(ExportGroup exportGroup, ExportMask exportMask, DbClient dbClient) {
List<ExportGroup> otherExportGroups = ExportMaskUtils.getExportGroups(dbClient, exportMask);
ExportGroup egToSkip = null;
for (ExportGroup eg : otherExportGroups) {
// do not include the ExportGroup requested for delete
if (eg.getId().equals(exportGroup.getId())) {
egToSkip = eg;
break;
}
}
otherExportGroups.remove(egToSkip);
if (!otherExportGroups.isEmpty()) {
_log.info("ExportMask {} is in use by these other ExportGroups: {}",
exportMask.getMaskName(), Joiner.on(',').join(otherExportGroups));
} else {
_log.info("ExportMask {} is not in use by any other ExportGroups.", exportMask.getMaskName());
}
return otherExportGroups;
}
/**
* Export Mask can be shared with multiple ExportGroups in the below case.
*
* 1. Create volumes from different projects and export to same compute resource.
* 2. Export Mask has both exclusive and shared volumes.
*
* A storage view for a host can be deleted, if there are shared volumes from multiple projects.
* A storage view cannot be deleted, if there are excluisve volumes on the storage view along with shared volumes.
* We have to return true only for Case 2.
*
* @return
*/
public static boolean exportMaskHasBothExclusiveAndSharedVolumes(ExportGroup current, List<ExportGroup> otherExportGroups,
ExportMask exportMask) {
for (ExportGroup exportGroup : otherExportGroups) {
// This piece of code gets executed only when all the initiators of
// Host are being asked to remove and the export mask is being shared.
if (ExportGroupType.Cluster.toString().equalsIgnoreCase(current.getType())
&& ExportGroupType.Host.toString().equalsIgnoreCase(exportGroup.getType()) &&
!CollectionUtils.isEmpty(exportGroup.getInitiators())) {
_log.info(
"Export Mask is being shared with other Export Groups, and the export Group {} type is different from the current processed {}."
+ "Assuming this mask contains both shared and exclusive volumes ,removing the initiators might affect.");
return true;
}
}
return false;
}
/**
* Returns true if the storage system implementation supports consistent HLU generation for cluster export.
*
* @param storage the storage system
* @return true, if the storage system supports consistent HLU generation
*/
public static boolean systemSupportsConsistentHLUGeneration(StorageSystem storage) {
String systemType = storage.getSystemType();
return (DiscoveredDataObject.Type.vmax.name().equals(systemType) || DiscoveredDataObject.Type.vnxblock.name().equals(systemType)
|| DiscoveredDataObject.Type.xtremio.name().equals(systemType) || DiscoveredDataObject.Type.unity.name().equals(systemType)
|| DiscoveredDataObject.Type.vplex.name().equals(systemType));
}
/**
* Check to see if the validation variable is set. Default to true.
*
* @return true if the validation check is on.
*/
public static boolean isValidationEnabled() {
if (coordinator != null) {
return Boolean.valueOf(ControllerUtils
.getPropertyValueFromCoordinator(coordinator, VALIDATION_CHECK_PROPERTY));
} else {
_log.error("Bean wiring error: Coordinator not set, therefore validation will default to true.");
}
return true;
}
/**
* Selects and returns the list targets (storage ports) that need to be removed from
* an export mask when the initiators are removed from the storage group. If checks if
* the targets are used by other initiators before they can be removed. It returns
* an empty list if there are not any targets to remove.
*
* @param mask the export mask from which the initiator will be removed
* @param initiators the initiators being removed
* @return a list of targets that are no longer needed by an initiators.
*/
public static List<URI> getRemoveInitiatorStoragePorts(
ExportMask mask, List<Initiator> initiators, DbClient dbClient) {
// uris of ports candidates for removal
Set<URI> portUris = new HashSet<URI>();
List<URI> remainingInitiators = ExportUtils.getExportMaskAllInitiators(mask, dbClient); // ask Ameer - do i need this function
for (Initiator initiator : initiators) {
portUris.addAll(ExportUtils.getInitiatorPortsInMask(mask, initiator, dbClient));
remainingInitiators.remove(initiator.getId());
}
// for the remaining initiators, get the networks and check if the ports are in use
if (!remainingInitiators.isEmpty()) {
Iterator<Initiator> remInitiators = dbClient.queryIterativeObjects(Initiator.class,
remainingInitiators);
List<URI> initiatorPortUris = null;
while (remInitiators.hasNext()) {
Initiator initiator = remInitiators.next();
// stop looping if all the the ports are found to be in use
if (portUris.isEmpty()) {
break;
}
initiatorPortUris = ExportUtils.getInitiatorPortsInMask(mask, initiator, dbClient);
_log.info("Ports {} are in use in by initiator {} ",
initiatorPortUris, initiator.getInitiatorPort());
portUris.removeAll(initiatorPortUris);
}
}
_log.info("Ports {} are going to be removed", portUris);
return new ArrayList<URI>(portUris);
}
public static Map<String, List<URI>> mapInitiatorsToHostResource(
ExportGroup exportGroup, Collection<URI> initiatorURIs, DbClient dbClient) {
Map<String, List<URI>> hostInitiatorMap = new ConcurrentHashMap<String, List<URI>>();
// Bogus URI for those initiators without a host object, helps maintain a good map.
// We want to put bunch up the non-host initiators together.
URI fillerHostURI = URIUtil.createId(Host.class); // could just be NullColumnValueGetter.getNullURI()
if (!initiatorURIs.isEmpty()) {
for (URI newExportMaskInitiator : initiatorURIs) {
Initiator initiator = dbClient.queryObject(Initiator.class, newExportMaskInitiator);
// Not all initiators have hosts, be sure to handle either case.
URI hostURI = initiator.getHost();
if (hostURI == null) {
hostURI = fillerHostURI;
}
String hostURIStr = hostURI.toString();
List<URI> initiatorSet = hostInitiatorMap.get(hostURIStr);
if (initiatorSet == null) {
initiatorSet = new ArrayList<URI>();
hostInitiatorMap.put(hostURIStr, initiatorSet);
}
initiatorSet.add(initiator.getId());
hostInitiatorMap.get(hostURIStr).addAll(initiatorSet);
_log.info(String.format("host = %s, initiators to add: %d, ",
hostURI, hostInitiatorMap.get(hostURIStr).size()));
}
}
return hostInitiatorMap;
}
/**
* Method to clean ExportMask stale instances from ViPR db if any stale EM available.
*
* @param storage the storage
* @param maskNamesFromArray Mask Names collected from Array for the set of initiator names
* @param initiatorNames initiator names
* @param dbClient
*/
public static void cleanStaleExportMasks(StorageSystem storage, Set<String> maskNamesFromArray, List<String> initiatorNames,
DbClient dbClient) {
Set<Initiator> initiators = ExportUtils.getInitiators(initiatorNames, dbClient);
Set<ExportMask> staleExportMasks = new HashSet<>();
_log.info("Mask Names found in array:{} for the initiators: {}", maskNamesFromArray, initiatorNames);
for (Initiator initiator : initiators) {
URIQueryResultList emUris = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getExportMaskInitiatorConstraint(initiator.getId().toString()),
emUris);
ExportMask exportMask = null;
for (URI emUri : emUris) {
_log.debug("Export Mask URI :{}", emUri);
exportMask = dbClient.queryObject(ExportMask.class, emUri);
if (exportMask != null && !exportMask.getInactive() && storage.getId().equals(exportMask.getStorageDevice())) {
if (!maskNamesFromArray.contains(exportMask.getMaskName())) {
_log.info("Export Mask {} is not found in array", exportMask.getMaskName());
List<ExportGroup> egList = ExportUtils.getExportGroupsForMask(exportMask.getId(), dbClient);
if (CollectionUtils.isEmpty(egList)) {
_log.info("Found a stale export mask {} - {} and it can be removed from DB", exportMask.getId(),
exportMask.getMaskName());
staleExportMasks.add(exportMask);
} else {
_log.info("Export mask is having association with ExportGroup {}", egList);
}
}
}
}
}
if (!CollectionUtils.isEmpty(staleExportMasks)) {
dbClient.markForDeletion(staleExportMasks);
_log.info("Deleted {} stale export masks from DB", staleExportMasks.size());
}
_log.info("Export Mask cleanup activity done");
}
/**
* Determines if any of the initiators in the existing initiator list do NOT belong to the
* compute resource sent in. This is useful when trying to determine the proper export operation.
*
* Note: We intentionally avoid looking for existing non-managed initiators for VPLEX back-end and
* RP front-end masks. Those initiator types do not have a host URI and therefore will be exempt
* from this ever returning true.
*
* @param exportMask
* export mask
* @param computeResourceId
* host or cluster
* @param _dbClient
* dbclient
* @return true if any of the existing initiators aren't in the compute resource. false otherwise.
*/
public static boolean checkIfAnyExistingInitiatorsNotInComputeResource(ExportMask exportMask, URI computeResourceId,
DbClient _dbClient) {
// Assertions
if (exportMask == null || _dbClient == null) {
_log.error("Invalid argument sent to method.");
throw DeviceControllerException.exceptions.invalidObjectNull();
}
// VPLEX back-end and RP masks will not have a compute resource, and are exempt from this check
if (NullColumnValueGetter.isNullURI(computeResourceId)) {
return false;
}
// You need existing initiator in order to have one not be in a compute resource, so start there.
if (!exportMask.hasAnyExistingInitiators()) {
return false;
}
// This will give us any existing initiator that controller knows about in the DB.
List<Initiator> existingKnownInitiators = ExportUtils.getExportMaskExistingInitiators(exportMask, _dbClient);
if (existingKnownInitiators == null) {
// Guaranteed to be an empty list at worst, still assert the returning value.
_log.error("Invalid existing known initiator list returned");
throw DeviceControllerException.exceptions.invalidObjectNull();
}
// If there are more existing initiator port entries than there are existingKnownInitiators
// DB entries, it's a guarantee that at least one of them is not in our compute resource
if (existingKnownInitiators.size() < exportMask.getExistingInitiators().size()) {
return true;
}
// Collect the compute resources IDs associated with the sent-in compute resource
// (which could be a cluster, so we need the hosts)
List<URI> computeResourceIds = Arrays.asList(computeResourceId);
// If this is a cluster, get all of the host IDs in that cluster
if (URIUtil.isType(computeResourceId, Cluster.class)) {
final List<Host> hosts = CustomQueryUtility
.queryActiveResourcesByConstraint(_dbClient, Host.class,
AlternateIdConstraint.Factory.getHostsByClusterId(computeResourceId.toString()));
if (hosts != null) {
computeResourceIds.addAll(URIUtil.toUris(hosts));
}
}
// Now check to see if any of the existing initiators belong to hosts in our list.
// If not, return false because one initiator is not in our compute resource.
for (Initiator existingKnownInitiator : existingKnownInitiators) {
if (!NullColumnValueGetter.isNullURI(existingKnownInitiator.getHost()) &&
(!computeResourceIds.contains(existingKnownInitiator.getHost()))) {
return true;
}
}
return false;
}
/**
* For ViPR-only delete operations, we use this method to remove the
* block object from the export group and export masks associated with
* the block object.
*
* @param boURI
* The BlockObject to remove from export masks
* @param addToExisting
* When true, adds the block object to the existing objects list from the mask.
* @param dbClient
* Database handle
*/
public static void cleanBlockObjectFromExports(URI boURI, boolean addToExisting, DbClient dbClient) {
_log.info("Cleaning block object {} from exports", boURI);
Map<URI, ExportGroup> exportGroupMap = new HashMap<URI, ExportGroup>();
Map<URI, ExportGroup> updatedExportGroupMap = new HashMap<URI, ExportGroup>();
Map<String, ExportMask> updatedExportMaskMap = new HashMap<String, ExportMask>();
BlockObject bo = BlockObject.fetch(dbClient, boURI);
URIQueryResultList exportGroupURIs = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(boURI), exportGroupURIs);
for (URI exportGroupURI : exportGroupURIs) {
_log.info("Cleaning block object from export group {}", exportGroupURI);
ExportGroup exportGroup = null;
if (exportGroupMap.containsKey(exportGroupURI)) {
exportGroup = exportGroupMap.get(exportGroupURI);
} else {
exportGroup = dbClient.queryObject(ExportGroup.class, exportGroupURI);
exportGroupMap.put(exportGroupURI, exportGroup);
}
if (exportGroup.hasBlockObject(boURI)) {
_log.info("Removing block object from export group");
exportGroup.removeVolume(boURI);
if (!updatedExportGroupMap.containsKey(exportGroupURI)) {
updatedExportGroupMap.put(exportGroupURI, exportGroup);
}
}
List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(dbClient, exportGroup);
for (ExportMask exportMask : exportMasks) {
if (exportMask.hasVolume(boURI)) {
_log.info(String.format("Cleaning block object from export mask [%s]", exportMask.forDisplay()));
StringMap exportMaskVolumeMap = exportMask.getVolumes();
String hluStr = exportMaskVolumeMap.get(boURI.toString());
exportMask.removeVolume(boURI);
exportMask.removeFromUserCreatedVolumes(bo);
// Add this volume to the existing volumes map for the
// mask, so that if the last ViPR created volume goes
// away, the physical mask will not be deleted.
if (addToExisting) {
_log.info("Adding to existing volumes");
exportMask.addToExistingVolumesIfAbsent(bo, hluStr);
}
if (!updatedExportMaskMap.containsKey(exportMask.getId().toString())) {
updatedExportMaskMap.put(exportMask.getId().toString(), exportMask);
}
}
}
}
if (!updatedExportGroupMap.isEmpty()) {
List<ExportGroup> updatedExportGroups = new ArrayList<ExportGroup>(
updatedExportGroupMap.values());
dbClient.updateObject(updatedExportGroups);
}
if (!updatedExportMaskMap.isEmpty()) {
List<ExportMask> updatedExportMasks = new ArrayList<ExportMask>(
updatedExportMaskMap.values());
dbClient.updateObject(updatedExportMasks);
}
}
/**
* Checks the passed in Export Group and determines if it requires cleanup. This is
* mainly used for internal EGs (VPLEX/RP) as they might not otherwise be cleaned up.
*
* @param exportGroup The Export Group to check
* @param dbClient DbClient reference
*/
public static void checkExportGroupForCleanup(ExportGroup exportGroup, DbClient dbClient) {
if (exportGroup != null && dbClient != null) {
// If there are no masks or volumes associated with this export group, and it's an internal (VPLEX/RP)
// export group, delete the export group automatically.
if ((exportGroup.checkInternalFlags(Flag.INTERNAL_OBJECT)) &&
(CollectionUtils.isEmpty(exportGroup.getVolumes())
|| CollectionUtils.isEmpty(ExportMaskUtils.getExportMasks(dbClient, exportGroup)))) {
_log.info(String.format("Marking export group [%s %s] for deletion.",
exportGroup.getLabel(), exportGroup.getId()));
dbClient.markForDeletion(exportGroup);
}
}
}
/**
* Cleans ExportGroup's stale references
*
* @param exportGroup
* @param dbClient
*/
public static void cleanStaleReferences(ExportGroup exportGroup, DbClient dbClient) {
if (null != exportGroup && !exportGroup.getInactive()) {
cleanStaleMaskReferences(exportGroup, dbClient);
cleanStaleInitiatorReferences(exportGroup, dbClient);
cleanStaleHostReferences(exportGroup, dbClient);
cleanStaleClusterReferences(exportGroup, dbClient);
cleanStaleVolumeReferences(exportGroup, dbClient);
dbClient.updateObject(exportGroup);
}
}
/**
* Cleans stale mask references from export group instance
*
* @param exportGroup {@link ExportGroup}
* @param dbClient {@link DbClient}
*/
private static void cleanStaleMaskReferences(ExportGroup exportGroup, DbClient dbClient) {
if (null == exportGroup || exportGroup.getInactive()) {
return;
}
// Clean stale export mask references from ExportGroup.
StringSet exportMasks = exportGroup.getExportMasks();
if (!CollectionUtils.isEmpty(exportMasks)) {
List<URI> staleMasks = new ArrayList<>();
List<URI> unstaleMasks = new ArrayList<>();
StringSet exportGroupInitiators = exportGroup.getInitiators();
for (String mask : exportMasks) {
boolean isStaleMask = false;
URI maskURI = null;
try {
maskURI = URI.create(mask);
} catch (Exception e) {
_log.error(e.getMessage(), e);
isStaleMask = true;
}
if (maskURI != null) {
ExportMask maskObj = dbClient.queryObject(ExportMask.class, maskURI);
if (maskObj != null && !CollectionUtils.isEmpty(maskObj.getInitiators())) {
isStaleMask = Sets.intersection(exportGroupInitiators, maskObj.getInitiators()).isEmpty();
} else {
isStaleMask = true;
}
}
if (isStaleMask) {
staleMasks.add(maskURI);
_log.info("Stale mask {} will be removed from Export Group {}", maskURI, exportGroup.getId());
} else {
unstaleMasks.add(maskURI);
}
}
if (!CollectionUtils.isEmpty(staleMasks)) {
exportGroup.removeExportMasks(staleMasks);
for (URI maskURI : staleMasks) {
List<ExportGroup> exportGroups = getExportGroupsForMask(maskURI, dbClient);
if (exportGroups.isEmpty() || (exportGroups.size() == 1 && exportGroups.get(0).getId().equals(exportGroup.getId()))) {
ExportMask maskObj = dbClient.queryObject(ExportMask.class, maskURI);
if (maskObj != null) {
_log.info("Deleting export mask {} because it is no longer in use by an export group", maskObj);
dbClient.removeObject(maskObj);
}
}
}
}
// Make sure the zoning map contains no stale entries for
// masks that are not stale.
if (!CollectionUtils.isEmpty(unstaleMasks)) {
cleanStaleZoningMapEntries(unstaleMasks, dbClient);
}
}
if (CollectionUtils.isEmpty(exportGroup.getExportMasks())
&& !exportGroup.checkInternalFlags(DataObject.Flag.INTERNAL_OBJECT)) {
// COP-27689 - Even if all the export masks got cleared, the export Group still remains with initiators and volumes.
// Clean up all the initiators, volumes and ports as there are no available export masks.
_log.info("There are no masks in the export Group {}-->{} after cleaning up stale masks.", exportGroup.getId(),
exportGroup.getLabel());
resetExportGroup(exportGroup, dbClient);
}
}
/**
* Cleanup any stale entries in the zoning maps for the export masks with the passed URIs.
*
* @param maskURIs The URIs of the export masks to examine.
* @param dbClient A reference to a database client.
*/
private static void cleanStaleZoningMapEntries(List<URI> maskURIs, DbClient dbClient) {
Iterator<ExportMask> maskIter = dbClient.queryIterativeObjects(ExportMask.class, maskURIs);
while (maskIter.hasNext()) {
ExportMask maskObj = maskIter.next();
StringSetMap zoningMap = maskObj.getZoningMap();
StringSet maskInitIds = maskObj.getInitiators();
Set<String> zoningMapInitIds = new HashSet<>(zoningMap.keySet());
for (String zoningMapInitId : zoningMapInitIds) {
if (maskInitIds == null || maskInitIds.isEmpty() || !maskInitIds.contains(zoningMapInitId)) {
zoningMap.remove(zoningMapInitId);
}
}
maskObj.setZoningMap(zoningMap);
dbClient.updateObject(maskObj);
}
}
/**
* Cleans stale initiator references from export group instance
*
* @param exportGroup {@link ExportGroup}
* @param dbClient {@link DbClient}
*/
private static void cleanStaleInitiatorReferences(ExportGroup exportGroup, DbClient dbClient) {
if (null == exportGroup || exportGroup.getInactive()) {
return;
}
StringSet exportGroupInitiators = exportGroup.getInitiators();
// Clean Stale Mask references will delete export Group if export masks is empty, therefore at this point the masks will not be
// empty
if (!CollectionUtils.isEmpty(exportGroupInitiators)) {
List<URI> initiatorsNotInDB = new ArrayList<>();
Iterator<String> egInitiatorsIterator = exportGroupInitiators.iterator();
while (egInitiatorsIterator.hasNext()) {
URI initiatorURI = URI.create(egInitiatorsIterator.next());
Initiator initiatorObj = dbClient.queryObject(Initiator.class, initiatorURI);
if (initiatorObj == null || initiatorObj.getInactive()) {
_log.info("Removed stale initiator {} from export group {}", initiatorURI, exportGroup.forDisplay());
initiatorsNotInDB.add(initiatorURI);
}
}
exportGroup.removeInitiators(initiatorsNotInDB);
if (!CollectionUtils.isEmpty(exportGroup.getExportMasks())) {
Set<String> allMaskInitiators = new HashSet<>();
for (String mask : exportGroup.getExportMasks()) {
ExportMask maskObj = dbClient.queryObject(ExportMask.class, URI.create(mask));
if (maskObj != null && !CollectionUtils.isEmpty(maskObj.getInitiators())) {
allMaskInitiators.addAll(maskObj.getInitiators());
}
}
// Stale initiators = EG initiators - all initiators available in all the eg.masks
Set<String> staleInitiators = new HashSet<>(Sets.difference(exportGroupInitiators, allMaskInitiators));
if (!CollectionUtils.isEmpty(staleInitiators)) {
Collection<URI> staleInitiatorURIs = Collections2.transform(staleInitiators,
CommonTransformerFunctions.FCTN_STRING_TO_URI);
_log.info(String.format("Stale initiators [%s] will be removed from Export Group %s",
Joiner.on(',').join(staleInitiatorURIs), exportGroup.getId()));
exportGroup.removeInitiators(new ArrayList<>(staleInitiatorURIs));
}
}
}
if (CollectionUtils.isEmpty(exportGroup.getInitiators())
&& !exportGroup.checkInternalFlags(DataObject.Flag.INTERNAL_OBJECT)) {
// COP-27689 - Even if all the export masks got cleared, the export Group still remains with initiators and volumes.
// Clean up all the initiators, volumes and ports as there are no available export masks.
_log.info("There are no initiators in the export Group {}-->{} after cleaning up stale initiators.", exportGroup.getId(),
exportGroup.getLabel());
resetExportGroup(exportGroup, dbClient);
}
}
/**
* Get all the user Added volumes from all the masks in the export group.
* Compare the same with volumes with export group and remove stale volumes.
* After removal, if the volumes are empty in export Group, delete the export Group.
*
* @param exportGroup
* @param dbClient
*/
private static void cleanStaleVolumeReferences(ExportGroup exportGroup, DbClient dbClient) {
if (null == exportGroup || exportGroup.getInactive()) {
return;
}
// Clean Stale Mask references will delete export Group if export masks is empty, therefore at this point the masks will not be
// empty
if ((!CollectionUtils.isEmpty(exportGroup.getVolumes()) || !CollectionUtils.isEmpty(exportGroup.getSnapshots()))
&& !CollectionUtils.isEmpty(exportGroup.getExportMasks())) {
Set<String> exportGroupVolumes = new HashSet<>();
if (!CollectionUtils.isEmpty(exportGroup.getVolumes())) {
exportGroupVolumes.addAll(exportGroup.getVolumes().keySet());
}
if (!CollectionUtils.isEmpty(exportGroup.getSnapshots())) {
exportGroupVolumes.addAll(exportGroup.getSnapshots());
}
Set<String> volumesInAllMasks = new HashSet<String>();
// Export masks inside export Group will be limited in number
for (String mask : exportGroup.getExportMasks()) {
ExportMask maskObj = dbClient.queryObject(ExportMask.class, URI.create(mask));
if (null != maskObj && !CollectionUtils.isEmpty(maskObj.getUserAddedVolumes())) {
volumesInAllMasks.addAll(maskObj.getUserAddedVolumes().values());
}
}
// To Avoid concurrent modification exception created new set instance
Set<String> volumeDiff = new HashSet<>(Sets.difference(exportGroupVolumes, volumesInAllMasks));
if (!CollectionUtils.isEmpty(volumeDiff)) {
exportGroup.removeVolumes(new HashSet<String>(volumeDiff));
_log.info("Stale volumes/snapshots {} removed from Export Group {}", volumeDiff, exportGroup.getId());
}
}
if (CollectionUtils.isEmpty(exportGroup.getVolumes()) && CollectionUtils.isEmpty(exportGroup.getSnapshots())
&& !exportGroup.checkInternalFlags(DataObject.Flag.INTERNAL_OBJECT)) {
// COP-27689 - Even if all the export masks got cleared, the export
// Group still remains with initiators and volumes.
// Clean up all the initiators, volumes and ports as there are no
// available export masks.
_log.info("There are no volume/snapshot in the export Group {}-->{} after cleaning up stale volumes.", exportGroup.getId(),
exportGroup.getLabel());
resetExportGroup(exportGroup, dbClient);
}
}
/**
* Cleans stale host references from export group instance
*
* @param exportGroup {@link ExportGroup}
* @param dbClient {@link DbClient}
*/
private static void cleanStaleHostReferences(ExportGroup exportGroup, DbClient dbClient) {
if (null == exportGroup || exportGroup.getInactive()) {
return;
}
StringSet exportGroupInitiators = exportGroup.getInitiators();
// Clean Stale Initiator references will delete export Group if initiators are empty, therefore at this point the initiators will
// not be empty
if (!CollectionUtils.isEmpty(exportGroup.getHosts()) && !CollectionUtils.isEmpty(exportGroupInitiators)) {
Set<String> egHosts = new HashSet<>();
Collection<Initiator> initiators = Collections2.transform(exportGroupInitiators,
CommonTransformerFunctions.fctnStringToInitiator(dbClient));
for (Initiator initiator : initiators) {
// COP-27697 - The new migration handler code written by infra team would take care of removing the stale initiator uris on
// upgrade.
if (null != initiator && initiator.getHost() != null) {
egHosts.add(initiator.getHost().toString());
}
}
Set<String> staleHosts = new HashSet<>(Sets.difference(exportGroup.getHosts(), egHosts));
if (!CollectionUtils.isEmpty(staleHosts)) {
Collection<URI> staleHostURIs = Collections2.transform(staleHosts,
CommonTransformerFunctions.FCTN_STRING_TO_URI);
_log.info(String.format("Stale host references [%s] will be removed from Export Group %s",
Joiner.on(',').join(staleHostURIs), exportGroup.getId()));
exportGroup.removeHosts(new ArrayList<>(staleHostURIs));
}
}
if (!ExportGroupType.Initiator.toString().equalsIgnoreCase(exportGroup.getType())
&& CollectionUtils.isEmpty(exportGroup.getHosts())
&& !exportGroup.checkInternalFlags(DataObject.Flag.INTERNAL_OBJECT)) {
// COP-27689 - Even if all the export masks got cleared, the export Group still remains with initiators and volumes.
// Clean up all the initiators, volumes and ports as there are no available export masks.
_log.info("There are no hosts in the export Group {}-->{} after cleaning up stale hosts.", exportGroup.getId(),
exportGroup.getLabel());
resetExportGroup(exportGroup, dbClient);
}
}
/**
* Cleans stale cluster references from export group instance
*
* @param exportGroup {@link ExportGroup}
* @param dbClient {@link DbClient}
*/
private static void cleanStaleClusterReferences(ExportGroup exportGroup, DbClient dbClient) {
if (null == exportGroup || exportGroup.getInactive()) {
return;
}
StringSet exportGroupInitiators = exportGroup.getInitiators();
if (!CollectionUtils.isEmpty(exportGroup.getClusters()) && !CollectionUtils.isEmpty(exportGroupInitiators)) {
Set<String> egClusterURIs = new HashSet<>();
Collection<Host> hosts = Collections2.transform(exportGroup.getHosts(),
CommonTransformerFunctions.fctnStringToHost(dbClient));
for (Host host : hosts) {
if (host.getCluster() != null) {
egClusterURIs.add(host.getCluster().toString());
}
}
Set<String> staleClusters = new HashSet<>(Sets.difference(exportGroup.getClusters(), egClusterURIs));
if (!CollectionUtils.isEmpty(staleClusters)) {
Collection<URI> staleClusterURIs = Collections2.transform(staleClusters,
CommonTransformerFunctions.FCTN_STRING_TO_URI);
_log.info(String.format("Stale cluster references [%s] will be removed from Export Group %s",
Joiner.on(',').join(staleClusterURIs), exportGroup.getId()));
exportGroup.removeClusters(new ArrayList<>(staleClusterURIs));
}
}
if (ExportGroupType.Cluster.toString().equalsIgnoreCase(exportGroup.getType())
&& CollectionUtils.isEmpty(exportGroup.getClusters())
&& !exportGroup.checkInternalFlags(DataObject.Flag.INTERNAL_OBJECT)) {
// COP-27689 - Even if all the export masks got cleared, the export Group still remains with initiators and volumes.
// Clean up all the initiators, volumes and ports as there are no available export masks.
_log.info("There are no clusters in the export Group {}-->{} , after cleaning slate clusters.", exportGroup.getId(),
exportGroup.getLabel());
resetExportGroup(exportGroup, dbClient);
}
}
private static void resetExportGroup(ExportGroup exportGroup, DbClient dbClient) {
// Clean everything out of the group if there are no longer any
// masks after cleaning the stale masks. Note that we specifically
// do not delete the export group. This must be retained so that
// a reference is available to the northbound API.
exportGroup.removeInitiators(URIUtil.toURIList(exportGroup.getInitiators()));
exportGroup.removeHosts(URIUtil.toURIList(exportGroup.getHosts()));
exportGroup.removeClusters(URIUtil.toURIList(exportGroup.getClusters()));
if(!CollectionUtils.isEmpty(exportGroup.getVolumes())) {
exportGroup.removeVolumes(URIUtil.toURIList(exportGroup.getVolumes().keySet()));
}
exportGroup.removeExportMasks(URIUtil.toURIList(exportGroup.getExportMasks()));
dbClient.updateObject(exportGroup);
}
/**
* Handle the ExportMask Volume removal based on the ExportMaskToRemovedVolumeMap.
*
* @param dbClient a reference to the database client
* @param exportMaskToRemovedVolumeMap a map of ExportMask URI to Volume URIs to be removed
* @param exportGroupId the parent ExportGroup URI (used to determine "other" ExportGroups)
*/
public static void handleExportMaskVolumeRemoval(DbClient dbClient, Map<URI, List<URI>> exportMaskToRemovedVolumeMap,
URI exportGroupUri) {
if (null != exportMaskToRemovedVolumeMap) {
Map<URI, BlockObject> blockObjectCache = new HashMap<URI, BlockObject>();
for (Entry<URI, List<URI>> entry : exportMaskToRemovedVolumeMap.entrySet()) {
List<URI> volumeURIList = entry.getValue();
for (URI boURI : volumeURIList) {
if (!blockObjectCache.containsKey(boURI)) {
BlockObject blockObject = Volume.fetchExportMaskBlockObject(dbClient, boURI);
blockObjectCache.put(blockObject.getId(), blockObject);
}
}
ExportMask exportMask = dbClient.queryObject(ExportMask.class, entry.getKey());
if (null != exportMask && !exportMask.getInactive()) {
// Remove the volumes from the Export Mask.
exportMask.removeVolumes(volumeURIList);
for (URI volumeURI : volumeURIList) {
BlockObject blockObject = blockObjectCache.get(volumeURI);
if (blockObject != null) {
if (blockObject.getWWN() != null) {
exportMask.removeFromUserCreatedVolumes(blockObject);
} else {
_log.warn("Could not remove volume " + blockObject.getId() + " from export mask " + exportMask.getLabel() +
" because it does not have a WWN. Assumed not in mask, likely part of a rollback operation");
}
}
}
// if the ExportMask no longer has any user added volumes,
// remove it from any other ExportGroups it's associated with
// besides the exportGroupUri argument given to this method,
// and mark the ExportMask for deletion. the caller should
// do the work of removing the ExportMask from the ExportGroup
// it's working on because it likely has other operations that
// need to be saved as well to the ExportGroup.
if (!exportMask.hasAnyUserAddedVolumes()) {
_log.info("updating ExportGroups containing this ExportMask");
List<ExportGroup> exportGroups = ExportMaskUtils.getExportGroups(dbClient, exportMask);
for (ExportGroup eg : exportGroups) {
// only update ExportGroups besides the exportGroupUri argument --
// The reason being that the caller (the completer class) has an
// ExportGroup object loaded from the database for that URI already,
// and has already called removeExportMask. We don't want to update
// it here and then save it again at the end of the complete method.
if (!eg.getId().equals(exportGroupUri)) {
_log.info("Removing mask from ExportGroup " + eg.getGeneratedName());
eg.removeExportMask(exportMask.getId());
dbClient.updateObject(eg);
}
}
_log.info("marking this mask for deletion from ViPR: " + exportMask.getMaskName());
dbClient.markForDeletion(exportMask);
}
dbClient.updateObject(exportMask);
}
}
}
}
/**
* Get all LUNs on the array that mapped to a host identified by initiators in the mask
*
* @param dbClient
* @param exportMask
* @return LUNs mapped to the host
*/
public static Set<String> getAllLUNsForHost(DbClient dbClient, ExportMask exportMask) {
Set<String> lunIds = new HashSet<>();
URI storageUri = exportMask.getStorageDevice();
if (NullColumnValueGetter.isNullURI(storageUri)) {
return lunIds;
}
URI hostUri = null;
for (String init : exportMask.getInitiators()) {
Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(init));
if (initiator != null && !initiator.getInactive()) {
hostUri = initiator.getHost();
if (!NullColumnValueGetter.isNullURI(hostUri)) {
break;
}
}
}
// get initiators from host
Map<URI, ExportMask> exportMasks = new HashMap<>();
if (!NullColumnValueGetter.isNullURI(hostUri)) {
URIQueryResultList list = new URIQueryResultList();
dbClient.queryByConstraint(ContainmentConstraint.Factory.getContainedObjectsConstraint(hostUri, Initiator.class, "host"), list);
Iterator<URI> uriIter = list.iterator();
while (uriIter.hasNext()) {
URI initiatorId = uriIter.next();
URIQueryResultList egUris = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.
getExportGroupInitiatorConstraint(initiatorId.toString()), egUris);
ExportGroup exportGroup = null;
for (URI egUri : egUris) {
exportGroup = dbClient.queryObject(ExportGroup.class, egUri);
if (exportGroup == null || exportGroup.getInactive() || exportGroup.getExportMasks() == null) {
continue;
}
List<ExportMask> masks = ExportMaskUtils.getExportMasks(dbClient, exportGroup);
for (ExportMask mask : masks) {
if (mask != null &&
!mask.getInactive() &&
mask.hasInitiator(initiatorId.toString()) &&
mask.getVolumes() != null &&
storageUri.equals(mask.getStorageDevice())) {
exportMasks.put(mask.getId(), mask);
}
}
}
}
}
for (ExportMask mask : exportMasks.values()) {
StringMap volumeMap = mask.getVolumes();
if (volumeMap != null && !volumeMap.isEmpty()) {
for (String strUri : mask.getVolumes().keySet()) {
BlockObject bo = BlockObject.fetch(dbClient, URI.create(strUri));
if (bo != null && !bo.getInactive()) {
lunIds.add(bo.getNativeId());
}
}
}
}
return lunIds;
}
}