/* * Copyright (c) 2012-2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource.utils; import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.resource.ArgValidator; import com.emc.storageos.api.service.impl.response.RestLinkFactory; 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.Constraint; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.PrefixConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.BlockSnapshot.TechnologyType; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.FCZoneReference; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.StoragePort; import com.emc.storageos.db.client.model.StoragePort.TransportType; import com.emc.storageos.db.client.model.StorageProtocol.Block; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.util.StringSetUtil; import com.emc.storageos.db.client.util.WWNUtility; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.model.RestLinkRep; import com.emc.storageos.model.block.export.ITLRestRep; import com.emc.storageos.model.block.export.ITLRestRepList; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.ACL; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.util.NetworkLite; import com.emc.storageos.util.NetworkUtil; import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils; public class ExportUtils { // Logger private final static Logger _log = LoggerFactory.getLogger(ExportUtils.class); /** * Returns initiators-to-target-storage-ports pairing for all initiators to which * the block object (volume or snapshot) was exported. * * @param id the URN of a ViPR block object * @param dbClient dbClient an instance of {@link DbClient} * @param idEmbeddedInURL * @return initiators-to-target-storage-ports pairing for all initiators to which * the block object (volume or snapshot) was exported. */ public static ITLRestRepList getBlockObjectInitiatorTargets(URI id, DbClient dbClient, boolean idEmbeddedInURL) { BlockObject blockObject = getBlockObject(id, dbClient); ArgValidator.checkEntityNotNull(blockObject, id, idEmbeddedInURL); ITLRestRepList list = new ITLRestRepList(); Map<ExportMask, List<ExportGroup>> exportMasks = getBlockObjectExportMasks(blockObject, dbClient); Collection<Initiator> initiators = null; List<StoragePort> ports = null; List<StoragePort> initiatorPorts = null; BlockObject bo = Volume.fetchExportMaskBlockObject(dbClient, blockObject.getId()); if (bo != null) { Map<StoragePort, List<FCZoneReference>> zoneRefs = null; for (ExportMask exportMask : exportMasks.keySet()) { // now process the initiators - Every initiator must see the volume initiators = getInitiators(exportMask, dbClient); _log.debug("Found {} initiators in export mask {}", initiators.size(), exportMask.getMaskName()); ports = getStoragePorts(exportMask, dbClient); _log.debug("Found {} storage ports in export mask {}", ports.size(), exportMask.getMaskName()); String hlu = exportMask.getVolumes().get(bo.getId().toString()); _log.debug("Start pairing initiators and targets in export mask {}.", exportMask.getMaskName()); for (Initiator initiator : initiators) { initiatorPorts = getInitiatorPorts(exportMask, initiator, ports, dbClient); zoneRefs = getInitiatorsZoneReferencesForBlockObject(initiator, initiatorPorts, bo, dbClient); list.getExportList().addAll(getItlsForMaskInitiator(dbClient, exportMasks.get(exportMask), exportMask, initiator, hlu, blockObject, initiatorPorts, zoneRefs)); } } } _log.info("{} ITLs were found for block object {}.", list.getExportList().size(), blockObject.getLabel()); return list; } private static List<StoragePort> getInitiatorPorts(ExportMask exportMask, Initiator initiator, List<StoragePort> ports, DbClient dbClient) { List<StoragePort> initiatorPorts = new ArrayList<StoragePort>(); // Determine if WWN. Don't use the zoning map for iSCSI or other protocols. boolean isWWN = WWNUtility.isValidWWN(initiator.getInitiatorPort()); if (isWWN) { if (exportMask.getZoningMap() != null) { // ExportMask specifies zoning initiatorPorts = getZonedPorts(initiator, ports, exportMask.getZoningMap()); } } else { // for iscsi and other ip initiators, get all ports in the network NetworkLite network = NetworkUtil.getEndpointNetworkLite(initiator.getInitiatorPort(), dbClient); if (network != null && network.getTransportType().equals(TransportType.IP.toString())) { for (StoragePort port : ports) { if (network.getId().equals(port.getNetwork())) { initiatorPorts.add(port); } } } } return initiatorPorts; } /** * For each initiator in the list, return all the volumes and snapshots that * have been exported to it together with the target ports and zone name when * zoning has been performed. * * @param networkPorts a list of initiator WWNs or IQNs * @param dbClient dbClient an instance of {@link DbClient} * @param permissionsHelper an instance of {@link PermissionsHelper} * @param user a pointer to the logged in user * @return all the volumes and snapshots that have been exported to the * initiators together with the target ports and zone name when * zoning has been performed. */ public static ITLRestRepList getInitiatorsItls(List<String> networkPorts, DbClient dbClient, PermissionsHelper permissionsHelper, StorageOSUser user) throws DatabaseException { ITLRestRepList list = new ITLRestRepList(); List<String> invalidNetworkPorts = new ArrayList<String>(); Map<String, Initiator> initiators = new HashMap<String, Initiator>(); // get the initiators for (String networkPort : networkPorts) { Initiator initiator = null; if (initiators.containsKey(networkPort)) { continue; } initiator = getInitiator(networkPort, dbClient); if (initiator == null) { invalidNetworkPorts.add(networkPort); } else { initiators.put(networkPort, initiator); } } // check all the initiators exist and are active if (!invalidNetworkPorts.isEmpty()) { _log.warn("Could not find active initiator for the following initiator ports {}", invalidNetworkPorts); } for (Initiator initiator : initiators.values()) { list.getExportList().addAll(getItlsForInitiator(initiator, dbClient, permissionsHelper, user).getExportList()); } _log.info("{} ITLs were found for the request.", list.getExportList().size()); return list; } /** * Gets the list of exports (ITL) for one initiator. The list contains all the volumes and snapshots * that are exported to the initiator together with the target ports and the zones when zoning * was performed. * * @param initiator the initiator * @param dbClient an instance of {@link DbClient} * @param permissionsHelper an instance of {@link PermissionsHelper} * @param user a pointer to the logged in user * @return the list of ITLs for one initiator */ public static ITLRestRepList getItlsForInitiator(Initiator initiator, DbClient dbClient, PermissionsHelper permissionsHelper, StorageOSUser user) throws DatabaseException { ITLRestRepList list = new ITLRestRepList(); Map<ExportMask, List<ExportGroup>> exportMasks = null; exportMasks = getInitiatorExportMasks(initiator, dbClient, permissionsHelper, user); BlockObject blockObject = null; List<StoragePort> ports = null; List<StoragePort> initiatorPorts = null; Map<StoragePort, List<FCZoneReference>> zoneRefs = null; String hlu = ""; Map<String, BlockObject> blockObjects = getBlockObjectsForMasks(exportMasks.keySet(), dbClient); for (ExportMask exportMask : exportMasks.keySet()) { _log.info("Finding ITLs for initiator {} in export mask {}", initiator.getInitiatorPort(), exportMask.getMaskName()); ports = getStoragePorts(exportMask, dbClient); initiatorPorts = getInitiatorPorts(exportMask, initiator, ports, dbClient); zoneRefs = getInitiatorsZoneReferences(initiator, initiatorPorts, dbClient); for (String doUri : exportMask.getVolumes().keySet()) { hlu = exportMask.getVolumes().get(doUri); blockObject = blockObjects.get(doUri); if (blockObject != null) { list.getExportList().addAll(getItlsForMaskInitiator(dbClient, exportMasks.get(exportMask), exportMask, initiator, hlu, blockObject, initiatorPorts, zoneRefs)); } } } _log.info("{} ITLs were found for initiator {}.", list.getExportList().size(), initiator.getInitiatorPort()); return list; } private static Map<String, BlockObject> getBlockObjectsForMasks(Collection<ExportMask> exportMasks, DbClient dbClient) { Map<String, BlockObject> blockObjects = new HashMap<String, BlockObject>(); List<URI> masksURIs = getMaskVolumeURI(exportMasks); List<URI> volUris = new ArrayList<URI>(); List<URI> snapUris = new ArrayList<URI>(); for (URI uri : masksURIs) { if (URIUtil.isType(uri, BlockSnapshot.class)) { snapUris.add(uri); } else if (URIUtil.isType(uri, Volume.class)) { volUris.add(uri); } } Iterator<Volume> vols = dbClient.queryIterativeObjects(Volume.class, volUris, true); while (vols.hasNext()) { Volume vol = vols.next(); blockObjects.put(vol.getId().toString(), vol); } Iterator<BlockSnapshot> snaps = dbClient.queryIterativeObjects(BlockSnapshot.class, snapUris, true); while (snaps.hasNext()) { BlockSnapshot snap = snaps.next(); blockObjects.put(snap.getId().toString(), snap); } return blockObjects; } private static List<URI> getMaskVolumeURI(Collection<ExportMask> exportMasks) { List<URI> uris = new ArrayList<URI>(); for (ExportMask exportMask : exportMasks) { Collection<String> strUris = exportMask.getVolumes().keySet(); for (String strUri : strUris) { URI uri = URI.create(strUri); if (!uris.contains(uri)) { uris.add(uri); } } } return uris; } /** * 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. */ private 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; } /** * For a volume/snapshot-initiator pair find the target ports for the initiator in the list of * ports and find the zone name if zoning was performed. Return a list of ITLs for * each volume-initiator-target found. * * @param dbClient db client * @param exportMask the export mask the initiator is in * @param initiator the initiator * @param hlu the lun Id used by the initiator for the volume * @param blockObject the volume or snapshot * @param initiatorPorts ports to which the initiator is zoned in the export mask * @param zoneRefs a map of port-to-zone-reference * @param exportGroup the export groups in this export mask * @return all ITLs for a volume/snapshot-initiator pair. */ private static List<ITLRestRep> getItlsForMaskInitiator( DbClient dbClient, List<ExportGroup> exportGroups, ExportMask exportMask, Initiator initiator, String hlu, BlockObject blockObject, List<StoragePort> initiatorPorts, Map<StoragePort, List<FCZoneReference>> zoneRefs) { List<ITLRestRep> list = new ArrayList<ITLRestRep>(); Map<StoragePort, FCZoneReference> initiatorZoneRefs = null; // Find the block object that would appear in the Export Mask BlockObject bo = Volume.fetchExportMaskBlockObject(dbClient, blockObject.getId()); if (bo != null) { _log.debug("Finding target ports for initiator {} and block object {}", initiator.getInitiatorPort(), bo.getNativeGuid()); initiatorZoneRefs = getZoneReferences(bo.getId(), initiator, initiatorPorts, zoneRefs); _log.debug("{} target ports and {} SAN zones were found for initiator {} and block object {}", new Object[] { initiatorPorts.size(), initiatorZoneRefs.size(), initiator.getInitiatorPort(), bo.getNativeGuid() }); // TODO - Should we add special handling of iscsi initiators? for (ExportGroup exportGroup : exportGroups) { if (exportGroup.getVolumes() != null && exportGroup.getVolumes().containsKey(blockObject.getId().toString()) && exportGroup.getInitiators() != null && exportGroup.getInitiators().contains(initiator.getId().toString())) { // We want to check if there are any ports in this export group for this initiator List<StoragePort> portsInExportGroupVarray = filterPortsInVarray( exportGroup, exportMask.getStorageDevice(), initiatorPorts); if (!portsInExportGroupVarray.isEmpty()) { for (StoragePort port : portsInExportGroupVarray) { list.add(createInitiatorTargetRefRep(exportGroup, blockObject, hlu, initiator, port, initiatorZoneRefs.get(port))); } } else { list.add(createInitiatorTargetRefRep(exportGroup, blockObject, hlu, initiator, null, null)); } } } } return list; } private static List<StoragePort> filterPortsInVarray(ExportGroup exportGroup, URI storageSystemUri, List<StoragePort> initiatorPorts) { List<StoragePort> ports = new ArrayList<StoragePort>(); String varray = exportGroup.getVirtualArray().toString(); String altVarrayUri = null; if (exportGroup.getAltVirtualArrays() != null && exportGroup.getAltVirtualArrays().containsKey(storageSystemUri.toString())) { altVarrayUri = exportGroup.getAltVirtualArrays().get(storageSystemUri.toString()); _log.debug("Found an alternative varray {}", altVarrayUri); } for (StoragePort port : initiatorPorts) { if (port.getTaggedVirtualArrays() != null) { if (port.getTaggedVirtualArrays().contains(varray)) { _log.debug("Port {} was found to be in varray {}", port.getPortNetworkId(), varray); ports.add(port); } else if (altVarrayUri != null && port.getTaggedVirtualArrays().contains(altVarrayUri)) { _log.debug("Port {} was found to be in alternative varray {}", port.getPortNetworkId(), altVarrayUri); ports.add(port); } } else { _log.debug("Port {} was not found in the export group varray or alternate varray", port.getPortNetworkId()); } } return ports; } /** * Find the block object, volume or snapshot, for a given URI. * * @param id the URN of a ViPR block object, volume or snapshot * @param dbClient dbClient an instance of {@link DbClient} * @return the volume or snapshot for the given URI */ private static BlockObject getBlockObject(URI id, DbClient dbClient) { BlockObject blockObject = BlockObject.fetch(dbClient, id); if (blockObject != null && !blockObject.getInactive()) { return blockObject; } return null; } /** * Return the ports that were zoned to an initiator according to the zoningMap. * * @param initiator - Initiator * @param ports - List of ports in the ExportMask * @param zoningMap - zoningMap in the ExportMask * @return */ private static List<StoragePort> getZonedPorts(Initiator initiator, List<StoragePort> ports, StringSetMap zoningMap) { List<StoragePort> zonedPorts = new ArrayList<StoragePort>(); if (zoningMap == null || zoningMap.isEmpty()) { return zonedPorts; } StringSet portSet = zoningMap.get(initiator.getId().toString()); if (portSet != null) { for (StoragePort port : ports) { if (portSet.contains(port.getId().toString())) { zonedPorts.add(port); } } } return zonedPorts; } /** * Creates and returns an instance of REST response object. * * @param exportGroup the export group the initiator is in * @param blockObject the block object * @param hlu the lun id used for the block object by the initiator * @param initiator the initiator * @param port the target port * @param fcZoneReference the record of the SAN zone created on the switch when a zone is created. * Returns null when zoning is not required or when the zone creation failed. * @return the export REST response object. */ private static ITLRestRep createInitiatorTargetRefRep(ExportGroup exportGroup, BlockObject blockObject, String hlu, Initiator initiator, StoragePort port, FCZoneReference fcZoneReference) { ITLRestRep rep = new ITLRestRep(); rep.setHlu(Integer.parseInt(hlu)); // Set block object ITLRestRep.ITLBlockObjectRestRep blockObjectRestRep = new ITLRestRep.ITLBlockObjectRestRep(); blockObjectRestRep.setId(blockObject.getId()); blockObjectRestRep.setLink(new RestLinkRep("self", RestLinkFactory.newLink(blockObject))); blockObjectRestRep.setWwn(getBlockObjectFormattedWWN(blockObject)); rep.setBlockObject(blockObjectRestRep); // Set initiator ITLRestRep.ITLInitiatorRestRep initiatorRestRep = new ITLRestRep.ITLInitiatorRestRep(); initiatorRestRep.setId(initiator.getId()); initiatorRestRep.setLink(new RestLinkRep("self", RestLinkFactory.newLink(initiator))); initiatorRestRep.setPort(initiator.getInitiatorPort()); rep.setInitiator(initiatorRestRep); // Set storage port ITLRestRep.ITLStoragePortRestRep storagePortRestRep = new ITLRestRep.ITLStoragePortRestRep(); if (port != null) { storagePortRestRep.setId(port.getId()); storagePortRestRep.setLink(new RestLinkRep("self", RestLinkFactory.newLink(port))); storagePortRestRep.setPort(port.getPortNetworkId()); if (port.getIpAddress() != null) { storagePortRestRep.setIpAddress(port.getIpAddress()); storagePortRestRep.setTcpPort(String.valueOf(port.getTcpPortNumber())); } } rep.setStoragePort(storagePortRestRep); // Export rep.setExport(toNamedRelatedResource(exportGroup, exportGroup.getLabel())); if (fcZoneReference != null) { rep.setSanZoneName(fcZoneReference.getZoneName()); } return rep; } private static String getBlockObjectFormattedWWN(BlockObject blockObject) { if (blockObject.getWWN() == null) { return null; } return blockObject.getWWN().toUpperCase(); } /** * Find the san zone information for the initiator and storage ports. Returns * a map of zone references per port. * * @param initiator the initiator * @param ports the target ports * @param dbClient an instance of {@link DbClient} * @return a map of san zones created for the initiator grouped by port for * the list of target ports. Otherwise, an returns empty map. */ private static Map<StoragePort, List<FCZoneReference>> getInitiatorsZoneReferences(Initiator initiator, List<StoragePort> ports, DbClient dbClient) { Map<StoragePort, List<FCZoneReference>> targetPortReferences = new HashMap<StoragePort, List<FCZoneReference>>(); if (initiator.getProtocol().equals(Block.FC.name())) { List<FCZoneReference> refs = null; for (StoragePort port : ports) { String key = FCZoneReference.makeEndpointsKey( Arrays.asList(initiator.getInitiatorPort(), port.getPortNetworkId())); refs = new ArrayList<FCZoneReference>(); targetPortReferences.put(port, refs); URIQueryResultList queryList = new URIQueryResultList(); dbClient.queryByConstraint(AlternateIdConstraint.Factory.getFCZoneReferenceKeyConstraint(key), queryList); Iterator<FCZoneReference> refsUris = dbClient.queryIterativeObjects(FCZoneReference.class, iteratorToList(queryList)); FCZoneReference ref = null; while (refsUris.hasNext()) { ref = refsUris.next(); if (ref != null && !ref.getInactive()) { refs.add(ref); } } } } return targetPortReferences; } /** * Find the san zone information for the initiator/block object. * * @param blockObjectUri the block object URI * @param initiator the initiator * @param ports the target ports * @param refs a map of port-to-zone-reference * @return the list of san zones created for the initiator if any were created. Otherwise, an returns empty map. */ private static Map<StoragePort, FCZoneReference> getZoneReferences(URI blockObjectUri, Initiator initiator, List<StoragePort> ports, Map<StoragePort, List<FCZoneReference>> refs) { Map<StoragePort, FCZoneReference> targetPortReferences = new HashMap<StoragePort, FCZoneReference>(); if (initiator.getProtocol().equals(Block.FC.name())) { for (StoragePort port : ports) { for (FCZoneReference ref : refs.get(port)) { if (ref != null && !ref.getInactive() && blockObjectUri.equals(ref.getVolumeUri())) { targetPortReferences.put(port, ref); break; // there should be one only } } } } _log.debug("Found {} san zone references for initiator {} and block object {}", new Object[] { targetPortReferences.size(), initiator.getInitiatorPort(), blockObjectUri }); return targetPortReferences; } /** * Find the san zone information for the initiator and storage ports. Returns * a map of zone references per port. * * @param initiator the initiator * @param ports the target ports * @param bo block object * @param dbClient an instance of {@link DbClient} * @return a map of san zones created for the initiator grouped by port for * the list of target ports. Otherwise, an returns empty map. */ private static Map<StoragePort, List<FCZoneReference>> getInitiatorsZoneReferencesForBlockObject( Initiator initiator, List<StoragePort> ports, BlockObject bo, DbClient dbClient) { Map<StoragePort, List<FCZoneReference>> targetPortReferences = new HashMap<StoragePort, List<FCZoneReference>>(); if (initiator.getProtocol().equals(Block.FC.name())) { List<FCZoneReference> refs = null; for (StoragePort port : ports) { String key = FCZoneReference.makeLabel( Arrays.asList(initiator.getInitiatorPort(), port.getPortNetworkId(), bo.getId().toString())); refs = new ArrayList<FCZoneReference>(); targetPortReferences.put(port, refs); URIQueryResultList queryList = new URIQueryResultList(); dbClient.queryByConstraint(PrefixConstraint.Factory.getLabelPrefixConstraint(FCZoneReference.class, key), queryList); while (queryList.iterator().hasNext()) { FCZoneReference ref = dbClient.queryObject(FCZoneReference.class, queryList.iterator().next()); if (ref != null && !ref.getInactive()) { refs.add(ref); } } } } return targetPortReferences; } /** * 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 */ private static List<StoragePort> getStoragePorts(ExportMask exportMask, DbClient dbClient) { List<StoragePort> ports = new ArrayList<StoragePort>(); StoragePort port = null; if (exportMask.getStoragePorts() != null) { for (String initUri : exportMask.getStoragePorts()) { port = dbClient.queryObject(StoragePort.class, URI.create(initUri)); if (port != null && !port.getInactive()) { ports.add(port); } } } _log.debug("Found {} stoarge ports in export mask {}", ports.size(), exportMask.getMaskName()); return ports; } /** * 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 */ private static Collection<Initiator> getInitiators(ExportMask exportMask, DbClient dbClient) { Map<String, Initiator> initiators = new HashMap<String, Initiator>(); Initiator initiator = null; if (exportMask.getInitiators() != null) { for (String initUri : exportMask.getInitiators()) { initiator = dbClient.queryObject(Initiator.class, URI.create(initUri)); if (initiator != null && !initiator.getInactive()) { initiators.put(initiator.getInitiatorPort(), initiator); } } } if (exportMask.getExistingInitiators() != null && !exportMask.getExistingInitiators().isEmpty()) { for (String initStr : exportMask.getExistingInitiators()) { if (!initiators.containsKey(initStr)) { initStr = Initiator.toPortNetworkId(initStr); Initiator init = getInitiator(initStr, dbClient); if (init != null) { initiators.put(initStr, init); } } } } return initiators.values(); } /** * 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 map of export masks in which a block object is member */ private static Map<ExportMask, List<ExportGroup>> getBlockObjectExportMasks(BlockObject blockObject, DbClient dbClient) { Map<ExportMask, List<ExportGroup>> exportMasks = new HashMap<ExportMask, List<ExportGroup>>(); ContainmentConstraint constraint = ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(blockObject.getId()); // permission are checked by the API service - no need to check again List<ExportGroup> exportGroups = getExportGroupsByConstraint(constraint, dbClient, null, null); List<ExportMask> masks = getMasksForExportGroups(exportGroups, dbClient); // Get the actual export block object associated with the snapshot (if applicable) BlockObject bo = Volume.fetchExportMaskBlockObject(dbClient, blockObject.getId()); if (bo != null) { for (ExportMask exportMask : masks) { if (exportMask != null && !exportMask.getInactive() && exportMask.hasVolume(bo.getId()) && (exportMask.getInitiators() != null || exportMask.getExistingInitiators() != null)) { List<ExportGroup> maskGroups = new ArrayList<ExportGroup>(); exportMasks.put(exportMask, maskGroups); for (ExportGroup group : exportGroups) { if (group.getExportMasks() != null && group.getExportMasks().contains(exportMask.getId().toString())) { maskGroups.add(group); } } } } _log.debug("Found {} export masks for block object {}", exportMasks.size(), bo.getLabel()); } return exportMasks; } /** * Gets all the export masks that this initiator is member of. * * @param initiator the initiator * @param dbClient an instance of {@link DbClient} * @param permissionsHelper an instance of {@link PermissionsHelper} * @param user a pointer to the logged in user * @return all the export masks that this initiator is member of */ private static Map<ExportMask, List<ExportGroup>> getInitiatorExportMasks(Initiator initiator, DbClient dbClient, PermissionsHelper permissionsHelper, StorageOSUser user) throws DatabaseException { Map<ExportMask, List<ExportGroup>> exportMasks = new HashMap<ExportMask, List<ExportGroup>>(); AlternateIdConstraint constraint = AlternateIdConstraint.Factory.getExportGroupInitiatorConstraint(initiator.getId().toString()); List<ExportGroup> exportGroups = getExportGroupsByConstraint(constraint, dbClient, permissionsHelper, user); List<ExportMask> masks = getMasksForExportGroups(exportGroups, dbClient); for (ExportMask exportMask : masks) { if (exportMask != null && !exportMask.getInactive() && (exportMask.hasInitiator(initiator.getId().toString()) || (exportMask.hasExistingInitiator(initiator))) && exportMask.getVolumes() != null) { List<ExportGroup> maskGroups = new ArrayList<ExportGroup>(); exportMasks.put(exportMask, maskGroups); for (ExportGroup group : exportGroups) { for (ExportMask em : ExportMaskUtils.getExportMasks(dbClient, group)) { if (em.getId().toString().equals(exportMask.getId().toString())) { maskGroups.add(group); } } } } } _log.info("Found {} export masks for initiator {}", exportMasks.size(), initiator.getInitiatorPort()); return exportMasks; } private static List<URI> iteratorToList(URIQueryResultList itr) { List<URI> uris = new ArrayList<URI>(); for (URI uri : itr) { uris.add(uri); } return uris; } private static List<ExportMask> getMasksForExportGroups(List<ExportGroup> exportGroups, DbClient dbClient) { List<URI> maskUris = new ArrayList<URI>(); for (ExportGroup exportGroup : exportGroups) { List<ExportMask> masks = ExportMaskUtils.getExportMasks(dbClient, exportGroup); for (ExportMask mask : masks) { if (!maskUris.contains(mask.getId())) { maskUris.add(mask.getId()); } } } return dbClient.queryObject(ExportMask.class, maskUris); } private static List<ExportGroup> getExportGroupsByConstraint(Constraint constraint, DbClient dbClient, PermissionsHelper permissionsHelper, StorageOSUser user) { List<ExportGroup> exportGroups = new ArrayList<ExportGroup>(); URIQueryResultList egUris = new URIQueryResultList(); dbClient.queryByConstraint(constraint, egUris); List<ExportGroup> queryExportGroups = dbClient.queryObject(ExportGroup.class, iteratorToList(egUris)); for (ExportGroup exportGroup : queryExportGroups) { if (exportGroup == null || exportGroup.getInactive() || ExportMaskUtils.getExportMasks(dbClient, exportGroup).isEmpty() || !checkUserPermissions(exportGroup, permissionsHelper, user)) { continue; } exportGroups.add(exportGroup); } return exportGroups; } /** * Gets all the export masks that this initiator is member of. * * @param networkPort The initiator's port WWN or IQN. * @param dbClient an instance of {@link DbClient} * @param permissionsHelper an instance of {@link PermissionsHelper} * @param user a pointer to the logged in user * @return all the export masks that this initiator is member of */ public static Map<ExportMask, List<ExportGroup>> getInitiatorExportMasks( String networkPort, DbClient dbClient, PermissionsHelper permissionsHelper, StorageOSUser user) throws DatabaseException { Initiator initiator = getInitiator(networkPort, dbClient); if (initiator != null) { return getInitiatorExportMasks(initiator, dbClient, permissionsHelper, user); } return Collections.emptyMap(); } /** * Checks if the user has permission to see the exports for a given export groups. * The user has permissions if * <ul> * <li>The user has SYSTEM_ADMIN or SYSTEM_MONITOR role, or</li> * <li>The user is a TENANT_ADMIN or TENANT_MONITOR for the tenant org, or</li> * <li>The user has any permission to the export group's project.</li> * </ul> * * @param group the export group * @param permissionsHelper a reference to {@link PermissionsHelper} * @param user the user * @return true if the user has at least read permissions to the export group * @throws DatabaseException when a DB error occurs */ private static boolean checkUserPermissions(ExportGroup group, PermissionsHelper permissionsHelper, StorageOSUser user) throws DatabaseException { // if the user or helper is null, assume permission checks are not required if (permissionsHelper == null || user == null) { return true; } // full list if role is {Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR} if (permissionsHelper.userHasGivenRole(user, null, Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN)) { return true; } else { // otherwise, filter by only authorized to use Project project = permissionsHelper.getObjectById(group.getProject().getURI(), Project.class); return project != null && (permissionsHelper.userHasGivenRole(user, project.getTenantOrg().getURI(), Role.TENANT_ADMIN) || permissionsHelper.userHasGivenACL(user, project.getId(), ACL.ANY)); } } /** * Validates the given consistency group to ensure there are no RecoverPoint snapshots that have been * exported. If any RecoverPoint snapshots associated with the consistency group have been exported, * an exception will be thrown. * * @param dbClient the database client * @param consistencyGroupUri the consistency group URI */ public static void validateConsistencyGroupBookmarksExported(DbClient dbClient, URI consistencyGroupUri) { if (consistencyGroupUri == null) { // If the consistency group URI is null we cannot proceed with this validation so fail. throw APIException.badRequests.invalidConsistencyGroup(); } _log.info(String.format("Performing validation to ensure no RP bookmarks have been exported for consistency group %s.", consistencyGroupUri)); URIQueryResultList snapshotUris = new URIQueryResultList(); ContainmentConstraint constraint = ContainmentConstraint.Factory.getBlockSnapshotByConsistencyGroup(consistencyGroupUri); dbClient.queryByConstraint(constraint, snapshotUris); List<BlockSnapshot> blockSnapshots = dbClient.queryObject(BlockSnapshot.class, snapshotUris); for (BlockSnapshot snapshot : blockSnapshots) { if (TechnologyType.RP.name().equalsIgnoreCase(snapshot.getTechnologyType())) { _log.info(String.format("Examining RP bookmark %s to see if it has been exported.", snapshot.getId())); // We have found an RP bookmark. Now lets see if that bookmark has been exported. Using the same // call that BlockSnapshotService uses to get snapshot exports. ContainmentConstraint exportGroupConstraint = ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(snapshot .getId()); URIQueryResultList exportGroupIdsForSnapshot = new URIQueryResultList(); dbClient.queryByConstraint(exportGroupConstraint, exportGroupIdsForSnapshot); Iterator<URI> exportGroupIdsForSnapshotIter = exportGroupIdsForSnapshot.iterator(); if (exportGroupIdsForSnapshotIter != null && exportGroupIdsForSnapshotIter.hasNext()) { // The consistency group has a bookmark that is already exported so fail the operation. throw APIException.badRequests.cannotPerformOperationWithExportedBookmarks(snapshot.getId(), consistencyGroupUri); } } } _log.info(String.format("No RP bookmarks have been exported for consistency group %s.", consistencyGroupUri)); } }