/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.cinder; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.cinder.CinderConstants; import com.emc.storageos.cinder.CinderEndPointInfo; import com.emc.storageos.cinder.api.CinderApi; import com.emc.storageos.cinder.api.CinderApiFactory; import com.emc.storageos.cinder.model.VolumeAttachResponse; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.HostInterface.Protocol; import com.emc.storageos.db.client.model.Initiator; 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.StringSetUtil; import com.emc.storageos.exceptions.DeviceControllerErrors; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.svcs.errorhandling.model.ServiceError; import com.emc.storageos.util.ConnectivityUtil; import com.emc.storageos.volumecontroller.TaskCompleter; import com.emc.storageos.volumecontroller.impl.VolumeURIHLU; import com.emc.storageos.volumecontroller.impl.smis.ExportMaskOperations; import com.google.common.base.Joiner; public class CinderExportOperations implements ExportMaskOperations { private static final String WWPNS = "wwpns"; private static final String WWNNS = "wwnns"; private static Logger log = LoggerFactory.getLogger(CinderExportOperations.class); private DbClient dbClient; private CinderApiFactory cinderApiFactory; public void setDbClient(DbClient dbClient) { this.dbClient = dbClient; } public void setCinderApiFactory(CinderApiFactory cinderApiFactory) { this.cinderApiFactory = cinderApiFactory; } @Override public void createExportMask(StorageSystem storage, URI exportMaskId, VolumeURIHLU[] volumeURIHLUs, List<URI> targetURIList, List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} createExportMask START...", storage.getSerialNumber()); try { log.info("createExportMask: Export mask id: {}", exportMaskId); log.info("createExportMask: volume-HLU pairs: {}", Joiner.on(',').join(volumeURIHLUs)); log.info("createExportMask: initiators: {}", Joiner.on(',').join(initiatorList)); log.info("createExportMask: assignments: {}", Joiner.on(',').join(targetURIList)); log.info("User assigned HLUs will be ignored as Cinder does not support it."); ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumes = new ArrayList<Volume>(); // map to store target LUN id generated for each volume Map<URI, Integer> volumeToTargetLunMap = new HashMap<URI, Integer>(); // Map to store volume to initiatorTargetMap Map<Volume, Map<String, List<String>>> volumeToFCInitiatorTargetMap = new HashMap<>(); for (VolumeURIHLU volumeURIHLU : volumeURIHLUs) { URI volumeURI = volumeURIHLU.getVolumeURI(); Volume volume = dbClient.queryObject(Volume.class, volumeURI); volumes.add(volume); } attachVolumesToInitiators(storage, volumes, initiatorList, volumeToTargetLunMap, volumeToFCInitiatorTargetMap, exportMask); // Update targets in the export mask if (!volumeToFCInitiatorTargetMap.isEmpty()) { updateTargetsInExportMask(storage, volumes.get(0), volumeToFCInitiatorTargetMap, initiatorList, exportMask); } updateTargetLunIdInExportMask(volumeToTargetLunMap, exportMask); dbClient.updateObject(exportMask); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in AttachVolumes: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder .operationFailed("doAttachVolumes", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} createExportMask END...", storage.getSerialNumber()); } @Override public void deleteExportMask(StorageSystem storage, URI exportMaskId, List<URI> volumeURIList, List<URI> targetURIList, List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} deleteExportMask START...", storage.getSerialNumber()); try { log.info("Export mask id: {}", exportMaskId); if (volumeURIList != null) { log.info("deleteExportMask: volumes: {}", Joiner.on(',').join(volumeURIList)); } if (targetURIList != null) { log.info("deleteExportMask: assignments: {}", Joiner.on(',').join(targetURIList)); } if (initiatorList != null) { log.info("deleteExportMask: initiators: {}", Joiner.on(',').join(initiatorList)); } // There is no masking concept on Cinder to delete the export mask. // But before marking the task completer as ready, // detach the volumes from the initiators that are there in the export mask. ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumeList = new ArrayList<Volume>(); StringMap volumes = exportMask.getUserAddedVolumes(); StringMap initiators = exportMask.getUserAddedInitiators(); if (volumes != null) { for (String vol : volumes.values()) { URI volumeURI = URI.create(vol); volumeURIList.add(volumeURI); Volume volume = dbClient.queryObject(Volume.class, volumeURI); volumeList.add(volume); } } if (initiators != null) { for (String ini : initiators.values()) { Initiator initiatorObj = dbClient.queryObject(Initiator.class, URI.create(ini)); initiatorList.add(initiatorObj); } } log.info("deleteExportMask: volumes: {}", Joiner.on(',').join(volumeURIList)); log.info("deleteExportMask: assignments: {}", Joiner.on(',').join(targetURIList)); log.info("deleteExportMask: initiators: {}", Joiner.on(',').join(initiatorList)); detachVolumesFromInitiators(storage, volumeList, initiatorList); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in DetachVolumes: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder .operationFailed("doDetachVolumes", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} deleteExportMask END...", storage.getSerialNumber()); } @Override public void addVolumes(StorageSystem storage, URI exportMaskId, VolumeURIHLU[] volumeURIHLUs, List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} addVolumes START...", storage.getSerialNumber()); try { log.info("addVolumes: Export mask id: {}", exportMaskId); log.info("addVolumes: volume-HLU pairs: {}", Joiner.on(',').join(volumeURIHLUs)); if (initiatorList != null) { log.info("addVolumes: initiators impacted: {}", Joiner.on(',').join(initiatorList)); } log.info("User assigned HLUs will be ignored as Cinder does not support it."); ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumes = new ArrayList<Volume>(); List<Initiator> maskInitiatorList = new ArrayList<Initiator>(); // map to store target LUN id generated for each volume Map<URI, Integer> volumeToTargetLunMap = new HashMap<URI, Integer>(); StringMap initiators = exportMask.getUserAddedInitiators(); for (VolumeURIHLU volumeURIHLU : volumeURIHLUs) { URI volumeURI = volumeURIHLU.getVolumeURI(); Volume volume = dbClient.queryObject(Volume.class, volumeURI); volumes.add(volume); } for (String ini : initiators.values()) { Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(ini)); maskInitiatorList.add(initiator); } // Map to store volume to initiatorTargetMap Map<Volume, Map<String, List<String>>> volumeToFCInitiatorTargetMap = new HashMap<Volume, Map<String, List<String>>>(); attachVolumesToInitiators(storage, volumes, maskInitiatorList, volumeToTargetLunMap, volumeToFCInitiatorTargetMap, exportMask); // Update targets in the export mask if (!volumeToFCInitiatorTargetMap.isEmpty()) { updateTargetsInExportMask(storage, volumes.get(0), volumeToFCInitiatorTargetMap, maskInitiatorList, exportMask); } updateTargetLunIdInExportMask(volumeToTargetLunMap, exportMask); dbClient.updateObject(exportMask); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in AddVolumes: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder .operationFailed("doAddVolumes", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} addVolumes END...", storage.getSerialNumber()); } @Override public void removeVolumes(StorageSystem storage, URI exportMaskId, List<URI> volumeURIs, List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} removeVolumes START...", storage.getSerialNumber()); try { log.info("removeVolumes: Export mask id: {}", exportMaskId); log.info("removeVolumes: volumes: {}", Joiner.on(',').join(volumeURIs)); if (initiatorList != null) { log.info("removeVolumes: impacted initiators: {}", Joiner.on(",").join(initiatorList)); } ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumes = new ArrayList<Volume>(); List<Initiator> userAddedInitiatorList = new ArrayList<Initiator>(); StringMap initiators = exportMask.getUserAddedInitiators(); for (URI volumeURI : volumeURIs) { Volume volume = dbClient.queryObject(Volume.class, volumeURI); volumes.add(volume); } for (String ini : initiators.values()) { Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(ini)); userAddedInitiatorList.add(initiator); } detachVolumesFromInitiators(storage, volumes, userAddedInitiatorList); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in RemoveVolumes: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder .operationFailed("doRemoveVolumes", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} removeVolumes END...", storage.getSerialNumber()); } @Override public void addInitiators(StorageSystem storage, URI exportMaskId, List<URI> volumeURIs, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} addInitiators START...", storage.getSerialNumber()); try { log.info("addInitiators: Export mask id: {}", exportMaskId); if (volumeURIs != null) { log.info("addInitiators: volumes : {}", Joiner.on(',').join(volumeURIs)); } log.info("addInitiators: initiators : {}", Joiner.on(',').join(initiators)); log.info("addInitiators: targets : {}", Joiner.on(",").join(targets)); ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumeList = new ArrayList<Volume>(); // map to store target LUN id generated for each volume Map<URI, Integer> volumeToTargetLunMap = new HashMap<URI, Integer>(); StringMap volumes = exportMask.getUserAddedVolumes(); for (String vol : volumes.values()) { Volume volume = dbClient.queryObject(Volume.class, URI.create(vol)); volumeList.add(volume); } // Map to store volume to initiatorTargetMap Map<Volume, Map<String, List<String>>> volumeToFCInitiatorTargetMap = new HashMap<Volume, Map<String, List<String>>>(); attachVolumesToInitiators(storage, volumeList, initiators, volumeToTargetLunMap, volumeToFCInitiatorTargetMap, exportMask); // Update targets in the export mask if (!volumeToFCInitiatorTargetMap.isEmpty()) { updateTargetsInExportMask(storage, volumeList.get(0), volumeToFCInitiatorTargetMap, initiators, exportMask); dbClient.updateObject(exportMask); } // TODO : update volumeToTargetLunMap in export mask.? // Do we get different LUN ID for the new initiators from the same Host.? taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in AddInitiators: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder .operationFailed("doAddInitiators", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} addInitiators END...", storage.getSerialNumber()); } @Override public void removeInitiators(StorageSystem storage, URI exportMaskId, List<URI> volumeURIList, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { log.info("{} removeInitiators START...", storage.getSerialNumber()); try { log.info("removeInitiators: Export mask id: {}", exportMaskId); if (volumeURIList != null) { log.info("removeInitiators: volumes : {}", Joiner.on(',').join(volumeURIList)); } log.info("removeInitiators: initiators : {}", Joiner.on(',').join(initiators)); log.info("removeInitiators: targets : {}", Joiner.on(',').join(targets)); ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskId); List<Volume> volumeList = new ArrayList<Volume>(); StringMap volumes = exportMask.getUserAddedVolumes(); for (String vol : volumes.values()) { Volume volume = dbClient.queryObject(Volume.class, URI.create(vol)); volumeList.add(volume); } detachVolumesFromInitiators(storage, volumeList, initiators); taskCompleter.ready(dbClient); } catch (final Exception ex) { log.error("Problem in RemoveInitiators: ", ex); ServiceError serviceError = DeviceControllerErrors.cinder .operationFailed("doRemoveInitiators", ex.getMessage()); taskCompleter.error(dbClient, serviceError); } log.info("{} removeInitiators END...", storage.getSerialNumber()); } @Override public Map<String, Set<URI>> findExportMasks(StorageSystem storage, List<String> initiatorNames, boolean mustHaveAllPorts) throws DeviceControllerException { // not supported for Cinder. There are no masking concepts. So, return null. return null; } @Override public Set<Integer> findHLUsForInitiators(StorageSystem storage, List<String> initiatorNames, boolean mustHaveAllPorts) { // TODO Auto-generated method stub return null; } @Override public ExportMask refreshExportMask(StorageSystem storage, ExportMask mask) throws DeviceControllerException { // not supported for Cinder. There are no masking concepts. So, return the given mask as it is. return mask; } /** * Attaches volumes to initiators. * * @param storage * the storage * @param volumes * the volumes * @param initiators * the initiators * @param volumeToTargetLunMap * the volume to target lun map * @throws Exception * the exception */ private void attachVolumesToInitiators(StorageSystem storage, List<Volume> volumes, List<Initiator> initiators, Map<URI, Integer> volumeToTargetLunMap, Map<Volume, Map<String, List<String>>> volumeToInitiatorTargetMap, ExportMask exportMask) throws Exception { CinderEndPointInfo ep = CinderUtils.getCinderEndPoint( storage.getActiveProviderURI(), dbClient); log.debug("Getting the cinder APi for the provider with id {}", storage.getActiveProviderURI()); CinderApi cinderApi = cinderApiFactory.getApi( storage.getActiveProviderURI(), ep); List<Initiator> iSCSIInitiators = new ArrayList<Initiator>(); List<Initiator> fcInitiators = new ArrayList<Initiator>(); splitInitiatorsByProtocol(initiators, iSCSIInitiators, fcInitiators); String host = getHostNameFromInitiators(initiators); Map<String, String[]> mapSettingVsValues = getFCInitiatorsArray(fcInitiators); String[] fcInitiatorsWwpns = mapSettingVsValues.get(WWPNS); String[] fcInitiatorsWwnns = mapSettingVsValues.get(WWNNS); for (Volume volume : volumes) { // cinder generated volume ID String volumeId = volume.getNativeId(); int targetLunId = -1; VolumeAttachResponse attachResponse = null; // for iSCSI for (Initiator initiator : iSCSIInitiators) { String initiatorPort = initiator.getInitiatorPort(); log.debug(String .format("Attaching volume %s ( %s ) to initiator %s on Openstack cinder node", volumeId, volume.getId(), initiatorPort)); attachResponse = cinderApi.attachVolume( volumeId, initiatorPort, null, null, host); log.info("Got response : {}", attachResponse.connection_info.toString()); targetLunId = attachResponse.connection_info.data.target_lun; } // for FC if (fcInitiatorsWwpns.length > 0) { log.debug(String .format("Attaching volume %s ( %s ) to initiators %s on Openstack cinder node", volumeId, volume.getId(), fcInitiatorsWwpns)); attachResponse = cinderApi.attachVolume( volumeId, null, fcInitiatorsWwpns, fcInitiatorsWwnns, host); log.info("Got response : {}", attachResponse.connection_info.toString()); targetLunId = attachResponse.connection_info.data.target_lun; Map<String, List<String>> initTargetMap = attachResponse.connection_info.data.initiator_target_map; if (null != initTargetMap && !initTargetMap.isEmpty()) { volumeToInitiatorTargetMap.put(volume, attachResponse.connection_info.data.initiator_target_map); } } volumeToTargetLunMap.put(volume.getId(), targetLunId); // After the successful export, create or modify the storage ports CinderStoragePortOperations storagePortOperationsInstance = CinderStoragePortOperations.getInstance(storage, dbClient); storagePortOperationsInstance.invoke(attachResponse); } // Add ITLs to volume objects storeITLMappingInVolume(volumeToTargetLunMap, exportMask); } /** * Detaches volumes from initiators. * * @param storage * the storage * @param volumes * the volumes * @param initiators * the initiators * @throws Exception * the exception */ private void detachVolumesFromInitiators(StorageSystem storage, List<Volume> volumes, List<Initiator> initiators) throws Exception { CinderEndPointInfo ep = CinderUtils.getCinderEndPoint( storage.getActiveProviderURI(), dbClient); log.debug("Getting the cinder APi for the provider with id {}", storage.getActiveProviderURI()); CinderApi cinderApi = cinderApiFactory.getApi( storage.getActiveProviderURI(), ep); List<Initiator> iSCSIInitiators = new ArrayList<Initiator>(); List<Initiator> fcInitiators = new ArrayList<Initiator>(); splitInitiatorsByProtocol(initiators, iSCSIInitiators, fcInitiators); String host = getHostNameFromInitiators(initiators); Map<String, String[]> mapSettingVsValues = getFCInitiatorsArray(fcInitiators); String[] fcInitiatorsWwpns = mapSettingVsValues.get(WWPNS); String[] fcInitiatorsWwnns = mapSettingVsValues.get(WWNNS); for (Volume volume : volumes) { // cinder generated volume ID String volumeId = volume.getNativeId(); // for iSCSI for (Initiator initiator : iSCSIInitiators) { String initiatorPort = initiator.getInitiatorPort(); log.debug(String .format("Detaching volume %s ( %s ) from initiator %s on Openstack cinder node", volumeId, volume.getId(), initiatorPort)); cinderApi.detachVolume(volumeId, initiatorPort, null, null, host); // TODO : Do not use Job to poll status till we figure out how // to get detach status. /* * CinderJob detachJob = new CinderDetachVolumeJob(volumeId, * volume.getLabel(), storage.getId(), * CinderConstants.ComponentType.volume.name(), ep, * taskCompleter); ControllerServiceImpl.enqueueJob(new * QueueJob(detachJob)); */ } // for FC if (fcInitiatorsWwpns.length > 0) { log.debug(String .format("Detaching volume %s ( %s ) from initiator %s on Openstack cinder node", volumeId, volume.getId(), fcInitiatorsWwpns)); cinderApi.detachVolume(volumeId, null, fcInitiatorsWwpns, fcInitiatorsWwnns, host); } // If ITLs are added, remove them removeITLsFromVolume(volume); } } private void splitInitiatorsByProtocol(List<Initiator> initiatorList, List<Initiator> iSCSIInitiators, List<Initiator> fcInitiators) { for (Initiator initiator : initiatorList) { if (Protocol.iSCSI.name().equalsIgnoreCase(initiator.getProtocol())) { iSCSIInitiators.add(initiator); } else if (Protocol.FC.name().equalsIgnoreCase(initiator.getProtocol())) { fcInitiators.add(initiator); } } } private Map<String, String[]> getFCInitiatorsArray(List<Initiator> fcInitiators) { Map<String, String[]> mapSettingVsValues = new HashMap<>(); // form an array with all FC initiator wwpns // to put into attach request body String[] fcInitiatorsWwpns = new String[fcInitiators.size()]; String[] fcInitiatorsWwnns = new String[fcInitiators.size()]; int index = 0; for (Initiator fcInitiator : fcInitiators) { // remove colons in initiator port fcInitiatorsWwpns[index] = fcInitiator.getInitiatorPort().replaceAll(CinderConstants.COLON, ""); String wwnn = fcInitiator.getInitiatorNode(); if (null != wwnn && wwnn.length() > 0) { fcInitiatorsWwnns[index] = wwnn.replaceAll(CinderConstants.COLON, ""); } index++; } mapSettingVsValues.put(WWPNS, fcInitiatorsWwpns); mapSettingVsValues.put(WWNNS, fcInitiatorsWwnns); return mapSettingVsValues; } private String getHostNameFromInitiators(List<Initiator> initiators) { String host = null; for (Initiator initiator : initiators) { host = initiator.getHostName(); break; // all Initiators given belong to the same host } return host; } /** * Updates the target LUN ID for volumes in the export mask. * * @param volumeToTargetLunMap * @param exportMask */ private void updateTargetLunIdInExportMask( Map<URI, Integer> volumeToTargetLunMap, ExportMask exportMask) { for (URI volumeURI : volumeToTargetLunMap.keySet()) { Integer targetLunId = volumeToTargetLunMap.get(volumeURI); exportMask.getVolumes().put(volumeURI.toString(), targetLunId.toString()); } } @Override public void updateStorageGroupPolicyAndLimits(StorageSystem storage, ExportMask exportMask, List<URI> volumeURIs, VirtualPool newVirtualPool, boolean rollback, TaskCompleter taskCompleter) throws Exception { throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported(); } /** * Updates the initiator to target list map in the export mask * * @throws Exception */ private void updateTargetsInExportMask( StorageSystem storage, Volume volume, Map<Volume, Map<String, List<String>>> volumeToInitiatorTargetMapFromAttachResponse, List<Initiator> fcInitiatorList, ExportMask exportMask) throws Exception { log.debug("START - updateTargetsInExportMask"); // ITLS for initiator URIs vs Target port URIs - This will be the final // filtered list to send for the zoning map update Map<URI, List<URI>> mapFilteredInitiatorURIVsTargetURIList = new HashMap<URI, List<URI>>(); // From the initiators list, construct the map of initiator WWNs to // their URIs Map<String, URI> initiatorsWWNVsURI = getWWNvsURIFCInitiatorsMap(fcInitiatorList); URI varrayURI = volume.getVirtualArray(); /* * Get the list of storage ports from the storage system which are * associated with the varray This will be map of storage port WWNs with * their URIs */ Map<String, URI> mapVarrayTaggedPortWWNVsURI = getVarrayTaggedStoragePortWWNs(storage, varrayURI); // List of WWN entries, used below for filtering the target port list // from attach response Set<String> varrayTaggedPortWWNs = mapVarrayTaggedPortWWNVsURI.keySet(); URI vpoolURI = volume.getVirtualPool(); VirtualPool vpool = dbClient.queryObject(VirtualPool.class, vpoolURI); int pathsPerInitiator = vpool.getPathsPerInitiator(); // Process the attach response output Set<Volume> volumeKeysSet = volumeToInitiatorTargetMapFromAttachResponse.keySet(); for (Volume volumeRes : volumeKeysSet) { log.info(String .format("Processing attach response for the volume with URI %s and name %s", volumeRes.getId(), volumeRes.getLabel())); Map<String, List<String>> initiatorTargetMap = volumeToInitiatorTargetMapFromAttachResponse .get(volumeRes); Set<String> initiatorKeysSet = initiatorTargetMap.keySet(); for (String initiatorKey : initiatorKeysSet) { // The list of filtered target ports ( which are varray tagged ) // from the attach response List<String> filteredTargetList = filterTargetsFromResponse( varrayTaggedPortWWNs, initiatorTargetMap, initiatorKey); log.info(String.format( "For initiator %s accessible storage ports are %s ", initiatorKey, filteredTargetList.toString())); List<String> tmpTargetList = null; if (!isVplex(volumeRes)) { // For VPLEX - no path validations // Path validations are required only for the Host Exports tmpTargetList = checkPathsPerInitiator(pathsPerInitiator, filteredTargetList); if (null == tmpTargetList) { // Rollback case - throw the exception throw new Exception( String.format( "Paths per initiator criteria is not met for the initiator : %s " + " Target counts is: %s Expected paths per initiator is: %s", initiatorKey, String.valueOf(filteredTargetList.size()), String.valueOf(pathsPerInitiator))); } } else { tmpTargetList = filteredTargetList; } // Now populate URIs for the map to be returned - convert WWNs // to URIs populateInitiatorTargetURIMap( mapFilteredInitiatorURIVsTargetURIList, initiatorsWWNVsURI, mapVarrayTaggedPortWWNVsURI, initiatorKey, tmpTargetList); } // End initiator iteration } // End volume iteration // Clean all existing targets in the export mask and add new targets List<URI> storagePortListFromMask = StringSetUtil.stringSetToUriList(exportMask.getStoragePorts()); for (URI removeUri : storagePortListFromMask) { exportMask.removeTarget(removeUri); } exportMask.setStoragePorts(null); // Now add new target ports and populate the zoning map Set<URI> initiatorURIKeys = mapFilteredInitiatorURIVsTargetURIList.keySet(); for (URI initiatorURI : initiatorURIKeys) { List<URI> storagePortURIList = mapFilteredInitiatorURIVsTargetURIList.get(initiatorURI); for (URI portURI : storagePortURIList) { exportMask.addTarget(portURI); } } log.debug("END - updateTargetsInExportMask"); } /*** * Check if the request is for vplex. * * @param volume * @return */ private boolean isVplex(Volume volume) { boolean isVplex = false; VirtualPool vpool = dbClient.queryObject(VirtualPool.class, volume.getVirtualPool()); isVplex = VirtualPool.vPoolSpecifiesHighAvailability(vpool); return isVplex; } private Map<String, URI> getWWNvsURIFCInitiatorsMap( List<Initiator> fcInitiatorList) { log.debug("START - getWWNvsURIFCInitiatorsMap"); Map<String, URI> initiatorsWWNVsURI = new HashMap<String, URI>(); for (Initiator init : fcInitiatorList) { String wwnNoColon = Initiator.normalizePort(init.getInitiatorPort()); URI uri = init.getId(); initiatorsWWNVsURI.put(wwnNoColon, uri); } log.debug("END - getWWNvsURIFCInitiatorsMap"); return initiatorsWWNVsURI; } /** * Filters the target port list from the response based on the varray tagging. * * @param varrayTaggedPortWWNs * @param initiatorTargetMap * @param initiatorKey * @return */ private List<String> filterTargetsFromResponse( Set<String> varrayTaggedPortWWNs, Map<String, List<String>> initiatorTargetMap, String initiatorKey) { log.debug("START - filterTargetsFromResponse"); List<String> filteredTargetList = new ArrayList<String>(); List<String> targetPortListFromResponse = initiatorTargetMap.get(initiatorKey); for (String portWWN : targetPortListFromResponse) { // Some of the drivers returns ( for e.g NetApp unified driver ) // the response in lower case, hence it is required to do both checks. if (varrayTaggedPortWWNs.contains(portWWN) || varrayTaggedPortWWNs.contains(portWWN.toUpperCase())) { filteredTargetList.add(portWWN.toUpperCase()); } } log.debug("END - filterTargetsFromResponse"); return filteredTargetList; } /** * Checks if there are number of targets matching paths per initiator * assigned for the vpool. * * @param pathsPerInitiator * @param targetPathsList * @return */ private List<String> checkPathsPerInitiator(int pathsPerInitiator, List<String> targetPathsList) { log.debug("START - checkPathsPerInitiator"); List<String> tmpTargetList = new ArrayList<String>(); int targetPathsCount = targetPathsList.size(); if (targetPathsCount == pathsPerInitiator) { // Happy path, just update the targets list tmpTargetList.addAll(targetPathsList); } else if (targetPathsCount > pathsPerInitiator) { // Select the subset of ports if (1 == pathsPerInitiator) { tmpTargetList.add(targetPathsList.get(0)); } else { tmpTargetList.addAll(targetPathsList.subList(0, pathsPerInitiator)); } } else { return null; // rollback case } log.debug("END - checkPathsPerInitiator"); return tmpTargetList; } /** * * @param mapFilteredInitiatorURIVsTargetURIList * @param initiatorsWWNVsURI * @param mapVarrayTaggedPortWWNVsURI * @param initiatorKey * @param tmpTargetList */ private void populateInitiatorTargetURIMap( Map<URI, List<URI>> mapFilteredInitiatorURIVsTargetURIList, Map<String, URI> initiatorsWWNVsURI, Map<String, URI> mapVarrayTaggedPortWWNVsURI, String initiatorKey, List<String> tmpTargetList) { log.debug("START - populateInitiatorTargetURIMap"); // Convert target storage port wwns to uris List<URI> tmpTargetPortURIList = new ArrayList<URI>(); for (String portWWN : tmpTargetList) { tmpTargetPortURIList.add(mapVarrayTaggedPortWWNVsURI.get(portWWN)); } boolean isUpdateMap = false; URI initatiatorURI = initiatorsWWNVsURI.get(initiatorKey); if (mapFilteredInitiatorURIVsTargetURIList.containsKey(initatiatorURI)) { List<URI> existingTargetURIs = mapFilteredInitiatorURIVsTargetURIList.get(initatiatorURI); for (URI targetPortURI : tmpTargetPortURIList) { if (!existingTargetURIs.contains(targetPortURI)) { existingTargetURIs.add(targetPortURI); isUpdateMap = true; } } if (isUpdateMap) { mapFilteredInitiatorURIVsTargetURIList.put(initatiatorURI, existingTargetURIs); } } else { mapFilteredInitiatorURIVsTargetURIList.put(initatiatorURI, tmpTargetPortURIList); } log.debug("END - populateInitiatorTargetURIMap"); } /** * Gets the list of storage port WWNs which are tagged with the virtual * array. * * @param storage * StorageSystem from which ports needs to be queried. * @param varrayURI * URI of the tagged virtual array * @return */ private Map<String, URI> getVarrayTaggedStoragePortWWNs( StorageSystem storage, URI varrayURI) { log.debug("START - getVarrayTaggedStoragePortWWNs"); // Get the list of storage ports of a storage system which are varray // tagged Map<URI, List<StoragePort>> networkUriVsStoragePorts = ConnectivityUtil .getStoragePortsOfTypeAndVArray(dbClient, storage.getId(), StoragePort.PortType.frontend, varrayURI); Map<String, URI> varrayTaggedStoragePortWWNs = new HashMap<String, URI>(); Set<URI> networkUriSet = networkUriVsStoragePorts.keySet(); for (URI nwUri : networkUriSet) { List<StoragePort> ports = networkUriVsStoragePorts.get(nwUri); for (StoragePort port : ports) { log.info("Varray Tagged Port is " + port.getPortNetworkId()); String wwnNoColon = port.getPortNetworkId().replaceAll(CinderConstants.COLON, ""); varrayTaggedStoragePortWWNs.put(wwnNoColon, port.getId()); } } log.debug("END - getVarrayTaggedStoragePortWWNs"); return varrayTaggedStoragePortWWNs; } /** * Create Initiator Target LUN Mapping as an extension in volume object * * @param volume * - volume in which ITL to be added * @param exportMask * - exportMask in which the volume is to be added * @param targetLunId * - integer value of host LUN id on which volume is accessible. */ private void storeITLMappingInVolume(Map<URI, Integer> volumeToTargetLunMap, ExportMask exportMask) { log.debug("START - createITLMappingInVolume"); for (URI volumeURI : volumeToTargetLunMap.keySet()) { Integer targetLunId = volumeToTargetLunMap.get(volumeURI); Volume volume = dbClient.queryObject(Volume.class, volumeURI); StringSetMap zoningMap = exportMask.getZoningMap(); Set<String> zoningMapKeys = zoningMap.keySet(); int initiatorIndex = 0; for (String initiator : zoningMapKeys) { Initiator initiatorObj = dbClient.queryObject(Initiator.class, URI.create(initiator)); String initiatorWWPN = initiatorObj.getInitiatorPort().replaceAll(CinderConstants.COLON, ""); StringSet targetPorts = zoningMap.get(initiator); int targetIndex = 0; for (String target : targetPorts) { StoragePort targetPort = dbClient.queryObject(StoragePort.class, URI.create(target)); String targetPortWWN = targetPort.getPortNetworkId().replaceAll(CinderConstants.COLON, ""); // Format is - <InitiatorWWPN>-<TargetWWPN>-<LunId> String itl = initiatorWWPN + "-" + targetPortWWN + "-" + String.valueOf(targetLunId); // ITL keys will be formed as ITL-00, ITL-01, ITL-10, ITL-11 so on String itlKey = CinderConstants.PREFIX_ITL + String.valueOf(initiatorIndex) + String.valueOf(targetIndex); log.info(String.format("Adding ITL %s with key %s", itl, itlKey)); StringMap extensionsMap = volume.getExtensions(); if (null == extensionsMap) { extensionsMap = new StringMap(); extensionsMap.put(itlKey, itl); volume.setExtensions(extensionsMap); } else { volume.getExtensions().put(itlKey, itl); } targetIndex++; } initiatorIndex++; } dbClient.updateAndReindexObject(volume); } log.debug("END - createITLMappingInVolume"); } /** * Remove the list of ITLs from the volume extensions * * @param volume * @return */ private void removeITLsFromVolume(Volume volume) { StringMap extensions = volume.getExtensions(); Set<Map.Entry<String, String>> mapEntries = extensions.entrySet(); for (Iterator<Map.Entry<String, String>> it = mapEntries.iterator(); it.hasNext();) { Map.Entry<String, String> entry = it.next(); if (entry.getKey().startsWith(CinderConstants.PREFIX_ITL)) { it.remove(); } } } @Override public Map<URI, Integer> getExportMaskHLUs(StorageSystem storage, ExportMask exportMask) { return Collections.emptyMap(); } @Override public void addPaths(StorageSystem storage, URI exportMask, Map<URI, List<URI>> newPaths, TaskCompleter taskCompleter) throws DeviceControllerException { throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported(); } @Override public void removePaths(StorageSystem storage, URI exportMask, Map<URI, List<URI>> adjustedPaths, Map<URI, List<URI>> removePaths, TaskCompleter taskCompleter) throws DeviceControllerException { throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported(); } }