/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.xtremio;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.customconfigcontroller.CustomConfigConstants;
import com.emc.storageos.customconfigcontroller.DataSource;
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.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
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.NullColumnValueGetter;
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.ExportUtils;
import com.emc.storageos.util.InvokeTestFailure;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.VolumeURIHLU;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportMaskRemoveInitiatorCompleter;
import com.emc.storageos.volumecontroller.impl.smis.ExportMaskOperations;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.emc.storageos.volumecontroller.impl.utils.ExportOperationContext;
import com.emc.storageos.volumecontroller.impl.utils.ExportOperationContext.ExportOperationContextOperation;
import com.emc.storageos.volumecontroller.impl.validators.ValidatorFactory;
import com.emc.storageos.volumecontroller.impl.validators.contexts.ExportMaskValidationContext;
import com.emc.storageos.volumecontroller.impl.validators.xtremio.XtremIOExportMaskInitiatorsValidator;
import com.emc.storageos.volumecontroller.impl.validators.xtremio.XtremIOExportMaskVolumesValidator;
import com.emc.storageos.volumecontroller.impl.xtremio.prov.utils.XtremIOProvUtils;
import com.emc.storageos.workflow.WorkflowService;
import com.emc.storageos.xtremio.restapi.XtremIOClient;
import com.emc.storageos.xtremio.restapi.XtremIOConstants;
import com.emc.storageos.xtremio.restapi.XtremIOConstants.XTREMIO_ENTITY_TYPE;
import com.emc.storageos.xtremio.restapi.errorhandling.XtremIOApiException;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOInitiator;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOInitiatorGroup;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOLunMap;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOObjectInfo;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOTag;
import com.emc.storageos.xtremio.restapi.model.response.XtremIOVolume;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
public class XtremIOExportOperations extends XtremIOOperations implements ExportMaskOperations {
private static final Logger _log = LoggerFactory.getLogger(XtremIOExportOperations.class);
private ValidatorFactory validator;
public void setValidator(ValidatorFactory validator) {
this.validator = validator;
}
@Override
public void createExportMask(StorageSystem storage, URI exportMaskURI,
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: {}", exportMaskURI);
_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));
ExportOperationContext context = new XtremIOExportOperationContext();
// Prime the context object
taskCompleter.updateWorkflowStepContext(context);
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskURI);
if (exportMask == null || exportMask.getInactive()) {
throw new DeviceControllerException("Invalid ExportMask URI: " + exportMaskURI);
}
XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
List<Initiator> initiatorsToBeCreated = new ArrayList<Initiator>();
ArrayListMultimap<String, Initiator> initiatorToIGMap = XtremIOProvUtils.mapInitiatorToInitiatorGroup(
storage.getSerialNumber(),
initiatorList, initiatorsToBeCreated, xioClusterName, client);
runLunMapCreationAlgorithm(storage, exportMask, volumeURIHLUs, initiatorList,
targetURIList, client, xioClusterName, initiatorToIGMap, initiatorsToBeCreated, taskCompleter);
} catch (final Exception ex) {
_log.error("Problem in createExportMask: ", ex);
ServiceError serviceError = DeviceControllerErrors.xtremio
.operationFailed("createExportMask", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
_log.info("{} createExportMask END...", storage.getSerialNumber());
}
@Override
public void deleteExportMask(StorageSystem storage, URI exportMaskURI, 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: {}", exportMaskURI);
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));
}
List<URI> volumesToBeUnmapped = new ArrayList<URI>();
boolean isRollback = WorkflowService.getInstance().isStepInRollbackState(taskCompleter.getOpId());
if (isRollback) {
_log.info("Handling deleteExportMask as a result of rollback");
List<URI> addedVolumes = new ArrayList<URI>();
// Get the context from the task completer
ExportOperationContext context = (ExportOperationContext) WorkflowService.getInstance()
.loadStepData(taskCompleter.getOpId());
if (context != null && context.getOperations() != null) {
ListIterator li = context.getOperations().listIterator(context.getOperations().size());
while (li.hasPrevious()) {
ExportOperationContextOperation operation = (ExportOperationContextOperation) li.previous();
if (operation != null
&& XtremIOExportOperationContext.OPERATION_ADD_VOLUMES_TO_INITIATOR_GROUP
.equals(operation.getOperation())) {
addedVolumes = (List<URI>) operation.getArgs().get(0);
_log.info("Removing volumes {} as part of rollback", Joiner.on(',').join(addedVolumes));
}
}
}
volumesToBeUnmapped = addedVolumes;
if (volumesToBeUnmapped == null || volumesToBeUnmapped.isEmpty()) {
_log.info("There was no context found for add volumes. So there is nothing to rollback.");
taskCompleter.ready(dbClient);
return;
}
} else {
if (volumeURIList != null) {
volumesToBeUnmapped = volumeURIList;
}
}
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskURI);
if (exportMask == null || exportMask.getInactive()) {
throw new DeviceControllerException("Invalid ExportMask URI: " + exportMaskURI);
}
runLunMapDeletionOrRemoveInitiatorAlgorithm(storage, exportMask, volumesToBeUnmapped, initiatorList, taskCompleter);
} catch (final Exception ex) {
_log.error("Problem in deleteExportMask: ", ex);
ServiceError serviceError = DeviceControllerErrors.xtremio
.operationFailed("deleteExportMask", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
_log.info("{} deleteExportMask END...", storage.getSerialNumber());
}
@Override
public void addVolumes(StorageSystem storage, URI exportMaskURI, VolumeURIHLU[] volumeURIHLUs,
List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException {
_log.info("{} addVolumes START...", storage.getSerialNumber());
try {
_log.info("addVolumes: Export mask id: {}", exportMaskURI);
_log.info("addVolumes: volume-HLU pairs: {}", Joiner.on(',').join(volumeURIHLUs));
if (initiatorList != null) {
_log.info("addVolumes: initiators impacted: {}", Joiner.on(',').join(initiatorList));
}
ExportOperationContext context = new XtremIOExportOperationContext();
// Prime the context object
taskCompleter.updateWorkflowStepContext(context);
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskURI);
if (exportMask == null || exportMask.getInactive()) {
throw new DeviceControllerException("Invalid ExportMask URI: " + exportMaskURI);
}
XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
ArrayListMultimap<String, Initiator> initiatorToIGMap = XtremIOProvUtils.mapInitiatorToInitiatorGroup(
storage.getSerialNumber(),
initiatorList, null, xioClusterName, client);
XtremIOExportMaskInitiatorsValidator initiatorsValidator = (XtremIOExportMaskInitiatorsValidator) validator.addVolumes(storage,
exportMaskURI, initiatorList);
initiatorsValidator.setInitiatorToIGMap(initiatorToIGMap);
initiatorsValidator.validate();
runLunMapCreationAlgorithm(storage, exportMask, volumeURIHLUs, initiatorList, null, client, xioClusterName, initiatorToIGMap,
null, taskCompleter);
} catch (final Exception ex) {
_log.error("Problem in addVolumes: ", ex);
ServiceError serviceError = DeviceControllerErrors.xtremio
.operationFailed("addVolumes", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
_log.info("{} addVolumes END...", storage.getSerialNumber());
}
@Override
public void removeVolumes(StorageSystem storage, URI exportMaskURI, List<URI> volumeUris,
List<Initiator> initiatorList, TaskCompleter taskCompleter) throws DeviceControllerException {
_log.info("{} removeVolumes START...", storage.getSerialNumber());
try {
_log.info("removeVolumes: Export mask id: {}", exportMaskURI);
_log.info("removeVolumes: volumes: {}", Joiner.on(',').join(volumeUris));
if (initiatorList != null) {
_log.info("removeVolumes: impacted initiators: {}", Joiner.on(",").join(initiatorList));
}
boolean isRollback = WorkflowService.getInstance().isStepInRollbackState(taskCompleter.getOpId());
if (isRollback) {
_log.info("Handling removeVolumes as a result of rollback");
List<URI> addedVolumes = new ArrayList<URI>();
// Get the context from the task completer.
ExportOperationContext context = (ExportOperationContext) WorkflowService.getInstance()
.loadStepData(taskCompleter.getOpId());
if (context != null && context.getOperations() != null) {
ListIterator li = context.getOperations().listIterator(context.getOperations().size());
while (li.hasPrevious()) {
ExportOperationContextOperation operation = (ExportOperationContextOperation) li.previous();
if (operation != null
&& XtremIOExportOperationContext.OPERATION_ADD_VOLUMES_TO_INITIATOR_GROUP
.equals(operation.getOperation())) {
addedVolumes = (List<URI>) operation.getArgs().get(0);
_log.info("Removing volumes {} as part of rollback", Joiner.on(',').join(addedVolumes));
}
}
}
volumeUris = addedVolumes;
if (volumeUris == null || volumeUris.isEmpty()) {
_log.info("There was no context found for add volumes. So there is nothing to rollback.");
taskCompleter.ready(dbClient);
return;
}
}
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskURI);
if (exportMask == null || exportMask.getInactive()) {
throw new DeviceControllerException("Invalid ExportMask URI: " + exportMaskURI);
}
runLunMapDeletionAlgorithm(storage, exportMask, volumeUris, initiatorList, taskCompleter);
} catch (final Exception ex) {
_log.error("Problem in removeVolumes: ", ex);
ServiceError serviceError = DeviceControllerErrors.xtremio
.operationFailed("removeVolumes", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
_log.info("{} removeVolumes END...", storage.getSerialNumber());
}
@Override
public void addInitiators(StorageSystem storage, URI exportMaskURI, 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: {}", exportMaskURI);
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));
ExportOperationContext context = new XtremIOExportOperationContext();
// Prime the context object
taskCompleter.updateWorkflowStepContext(context);
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskURI);
if (exportMask == null || exportMask.getInactive()) {
throw new DeviceControllerException("Invalid ExportMask URI: " + exportMaskURI);
}
XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
List<Initiator> initiatorsToBeCreated = new ArrayList<Initiator>();
ArrayListMultimap<String, Initiator> initiatorToIGMap = XtremIOProvUtils.mapInitiatorToInitiatorGroup(
storage.getSerialNumber(),
initiators, initiatorsToBeCreated, xioClusterName, client);
XtremIOExportMaskVolumesValidator volumeValidator = (XtremIOExportMaskVolumesValidator) validator.addInitiators(storage,
exportMask, volumeURIs);
volumeValidator.setIgNames(initiatorToIGMap.keySet());
volumeValidator.validate();
Map<URI, Integer> map = new HashMap<URI, Integer>();
for (URI volumeURI : volumeURIs) {
String hlu = exportMask.getVolumes().get(volumeURI.toString());
if (NullColumnValueGetter.isNotNullValue(hlu)) {
map.put(volumeURI, Integer.parseInt(hlu));
}
}
// to make it uniform , using these structures
VolumeURIHLU[] volumeLunArray = ControllerUtils.getVolumeURIHLUArray(
storage.getSystemType(), map, dbClient);
runLunMapCreationAlgorithm(storage, exportMask, volumeLunArray, initiators, targets, client, xioClusterName, initiatorToIGMap,
initiatorsToBeCreated, taskCompleter);
} catch (final Exception ex) {
_log.error("Problem in addInitiators: ", ex);
ServiceError serviceError = DeviceControllerErrors.xtremio
.operationFailed("addInitiators", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
}
_log.info("{} addInitiators END...", storage.getSerialNumber());
}
@Override
public void removeInitiators(StorageSystem storage, URI exportMaskURI,
List<URI> volumeURIList, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter)
throws DeviceControllerException {
_log.info("{} removeInitiators START...", storage.getSerialNumber());
boolean isRollback = WorkflowService.getInstance().isStepInRollbackState(taskCompleter.getOpId());
if (isRollback) {
_log.info("Handling removeInitiators as a result of rollback");
List<Initiator> addedInitiators = new ArrayList<Initiator>();
// Get the context from the task completer.
ExportOperationContext context = (ExportOperationContext) WorkflowService.getInstance().loadStepData(taskCompleter.getOpId());
if (context != null && context.getOperations() != null) {
ListIterator li = context.getOperations().listIterator(context.getOperations().size());
while (li.hasPrevious()) {
ExportOperationContextOperation operation = (ExportOperationContextOperation) li.previous();
if (operation != null
&& XtremIOExportOperationContext.OPERATION_ADD_INITIATORS_TO_INITIATOR_GROUP.equals(operation.getOperation())) {
addedInitiators = (List<Initiator>) operation.getArgs().get(0);
_log.info("Removing initiators {} as part of rollback", Joiner.on(',').join(addedInitiators));
}
}
}
// Update the initiators in the task completer such that we update the export mask/group correctly
for (Initiator initiator : initiators) {
if (addedInitiators == null || !addedInitiators.contains(initiator)) {
((ExportMaskRemoveInitiatorCompleter) taskCompleter).removeInitiator(initiator.getId());
}
}
initiators = addedInitiators;
if (initiators == null || initiators.isEmpty()) {
_log.info("There was no context found for add initiator. So there is nothing to rollback.");
taskCompleter.ready(dbClient);
return;
}
}
ExportMask exportMask = dbClient.queryObject(ExportMask.class, exportMaskURI);
if (exportMask == null || exportMask.getInactive()) {
throw new DeviceControllerException("Invalid ExportMask URI: " + exportMaskURI);
}
XtremIOClient client = null;
// if host Name is not available in at least one of the initiator, then set it to
// Default_IG;
List<String> failedIGs = new ArrayList<String>();
ArrayListMultimap<String, Initiator> groupInitiatorsByIG = ArrayListMultimap.create();
try {
String hostName = null;
String clusterName = null;
client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
Iterator<Initiator> iniItr = initiators.iterator();
while (iniItr.hasNext()) {
Initiator initiator = iniItr.next();
String igName = null;
if (null != initiator.getHostName()) {
// initiators already grouped by Host
hostName = initiator.getHostName();
clusterName = initiator.getClusterName();
}
igName = XtremIOProvUtils.getIGNameForInitiator(initiator, storage.getSerialNumber(), client, xioClusterName);
if (igName != null && !igName.isEmpty()) {
groupInitiatorsByIG.put(igName, initiator);
} else {
// initiator not found in Array, remove from DB
exportMask.removeFromExistingInitiators(initiator);
exportMask.removeFromUserCreatedInitiators(initiator);
iniItr.remove();
}
}
// We need to look at all related initiators from the affected EM. We can use this list
// to then find all related volumes across all EMs. This will allow us to properly
// perform our validations.
List<Initiator> relatedInitiators = new ArrayList<Initiator>();
if (exportMask.getInitiators() != null && !exportMask.getInitiators().isEmpty()) {
Collection<URI> relatedInitiatorURIs = Collections2.transform(exportMask.getInitiators(),
CommonTransformerFunctions.FCTN_STRING_TO_URI);
relatedInitiators.addAll(dbClient.queryObject(Initiator.class, relatedInitiatorURIs));
} else {
relatedInitiators.addAll(initiators);
}
Set<URI> allRelatedVolumes = new HashSet<URI>();
allRelatedVolumes.addAll(findAllRelatedExportMaskVolumesForInitiator(relatedInitiators, exportMask.getStorageDevice()));
_log.info("removeInitiators: Export mask id: {}", exportMaskURI);
if (!CollectionUtils.isEmpty(allRelatedVolumes)) {
_log.info("removeInitiators: volumes : {}", Joiner.on(',').join(allRelatedVolumes));
}
_log.info("removeInitiators: initiators : {}", Joiner.on(',').join(initiators));
_log.info("removeInitiators: targets : {}", Joiner.on(',').join(targets));
_log.info("List of IGs found {} with size : {}",
Joiner.on(",").join(groupInitiatorsByIG.asMap().entrySet()), groupInitiatorsByIG.size());
ExportMaskValidationContext ctx = new ExportMaskValidationContext();
ctx.setStorage(storage);
ctx.setExportMask(exportMask);
ctx.setBlockObjects(allRelatedVolumes, dbClient);
ctx.setAllowExceptions(!isRollback);
XtremIOExportMaskVolumesValidator volumeValidator = (XtremIOExportMaskVolumesValidator) validator.removeInitiators(ctx);
volumeValidator.setIgNames(groupInitiatorsByIG.keySet());
volumeValidator.validate();
// Deleting the initiator automatically removes the initiator from
// lun map
for (Initiator initiator : initiators) {
try {
client.deleteInitiator(initiator.getMappedInitiatorName(storage.getSerialNumber()), xioClusterName);
exportMask.removeFromExistingInitiators(initiator);
exportMask.removeFromUserCreatedInitiators(initiator);
} catch (Exception e) {
failedIGs.add(initiator.getLabel().concat(XtremIOConstants.DASH).concat(e.getMessage()));
_log.warn("Removal of Initiator {} failed", initiator.getLabel(), e);
}
}
dbClient.updateObject(exportMask);
if (!failedIGs.isEmpty()) {
String errMsg = "Export Operations failed deleting these initiators: ".concat(Joiner.on(", ").join(
failedIGs));
ServiceError serviceError = DeviceControllerException.errors.jobFailedMsg(errMsg, null);
taskCompleter.error(dbClient, serviceError);
return;
}
// Clean IGs if empty
deleteInitiatorGroup(groupInitiatorsByIG, client, xioClusterName);
// delete IG Folder as well if IGs are empty
deleteInitiatorGroupFolder(client, xioClusterName, clusterName, hostName, storage);
taskCompleter.ready(dbClient);
} catch (Exception ex) {
_log.error("Problem in removeInitiators: ", ex);
ServiceError serviceError = DeviceControllerErrors.xtremio
.operationFailed("removeInitiators", ex.getMessage());
taskCompleter.error(dbClient, serviceError);
return;
}
_log.info("{} removeInitiators END...", storage.getSerialNumber());
}
@Override
public Map<String, Set<URI>> findExportMasks(StorageSystem storage,
List<String> initiatorNames, boolean mustHaveAllPorts) throws DeviceControllerException {
// TODO Auto-generated method stub
return null;
}
@Override
public Set<Integer> findHLUsForInitiators(StorageSystem storage, List<String> initiatorNames, boolean mustHaveAllPorts) {
Set<Integer> usedHLUs = new HashSet<Integer>();
try {
XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
Set<String> igNames = new HashSet<>();
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
for (String initiatorName : initiatorNames) {
initiatorName = Initiator.toPortNetworkId(initiatorName);
URIQueryResultList initiatorResult = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory.getInitiatorPortInitiatorConstraint(initiatorName),
initiatorResult);
if (initiatorResult.iterator().hasNext()) {
Initiator initiator = dbClient.queryObject(Initiator.class, initiatorResult.iterator().next());
String igName = XtremIOProvUtils.getIGNameForInitiator(initiator, storage.getSerialNumber(), client, xioClusterName);
if (igName != null && !igName.isEmpty()) {
igNames.add(igName);
}
}
}
// get the lun maps for IGs
for (String igName : igNames) {
List<XtremIOObjectInfo> lunMapLinks = XtremIOProvUtils.getInitiatorGroupLunMaps(igName, xioClusterName, client);
List<XtremIOLunMap> lunMaps = client.getXtremIOLunMapsForLinks(lunMapLinks, xioClusterName);
for (XtremIOLunMap lunMap : lunMaps) {
_log.info("Looking at lun map {}; IG name: {}, Volume: {}, HLU: {}",
lunMap.getMappingInfo().get(2), lunMap.getIgName(), lunMap.getVolumeName(), lunMap.getLun());
usedHLUs.add(Integer.valueOf(lunMap.getLun()));
}
}
_log.info(String.format("HLUs found for Initiators { %s }: %s",
Joiner.on(',').join(initiatorNames), usedHLUs));
} catch (Exception e) {
String errMsg = "Encountered an error when attempting to query used HLUs for initiators: " + e.getMessage();
_log.error(errMsg, e);
throw XtremIOApiException.exceptions.hluRetrievalFailed(errMsg, e);
}
return usedHLUs;
}
/*
* Refresh all export masks, not only the one passed in
* @see com.emc.storageos.volumecontroller.impl.smis.ExportMaskOperations#refreshExportMask(com.emc.storageos.db.client.model.StorageSystem, com.emc.storageos.db.client.model.ExportMask)
*/
@Override
public ExportMask refreshExportMask(StorageSystem storage, ExportMask mask) throws DeviceControllerException {
ExportMask maskToReturn = null;
try {
_log.info("Refreshing volumes and initiator labels in ViPR.. ");
XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
Set<String> igNameSet = new HashSet<>();
Map<URI, Set<String>> maskToIGNameMap = new HashMap<>();
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
List<XtremIOInitiator> initiators = client.getXtremIOInitiatorsInfo(xioClusterName);
List<Initiator> initiatorObjs = new ArrayList<Initiator>();
for (XtremIOInitiator initiator : initiators) {
URIQueryResultList initiatorResult = new URIQueryResultList();
dbClient
.queryByConstraint(AlternateIdConstraint.Factory.getInitiatorPortInitiatorConstraint(initiator.getPortAddress()),
initiatorResult);
if (initiatorResult.iterator().hasNext()) {
Initiator initiatorObj = dbClient.queryObject(Initiator.class, initiatorResult.iterator().next());
_log.info("Updating Initiator label from {} to {} in ViPR DB", initiatorObj.getLabel(), initiator.getName());
initiatorObj.setLabel(initiator.getName());
initiatorObj.mapInitiatorName(storage.getSerialNumber(), initiator.getName());
initiatorObjs.add(initiatorObj);
List<ExportMask> results = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, ExportMask.class,
ContainmentConstraint.Factory.getConstraint(ExportMask.class, "initiators", initiatorObj.getId()));
String igName = initiator.getInitiatorGroup().get(1);
for (ExportMask exportMask : results) {
if (exportMask != null && storage.getId().equals(exportMask.getStorageDevice())) {
igNameSet.add(igName);
// update export mask to IG name map
URI maskId = exportMask.getId();
Set<String> igNames = maskToIGNameMap.get(maskId);
if (igNames == null) {
igNames = new HashSet<String>();
maskToIGNameMap.put(maskId, igNames);
}
igNames.add(igName);
}
}
} else {
_log.info("No initiator objects in vipr db for port address {}", initiator.getPortAddress());
}
}
if (!initiatorObjs.isEmpty()) {
dbClient.updateObject(initiatorObjs);
}
// get volumes for each IG
Map<String, Map<String, Integer>> igNameToVolMap = new HashMap<>();
for (String igName: igNameSet) {
Map<String, Integer> discoveredVolumes = new HashMap<String, Integer>();
List<XtremIOVolume> igVolumes = XtremIOProvUtils.getInitiatorGroupVolumes(igName, xioClusterName, client);
for (XtremIOVolume igVolume : igVolumes) {
for (List<Object> lunMapEntries : igVolume.getLunMaps()) {
@SuppressWarnings("unchecked")
// This can't be null
List<Object> igDetails = (List<Object>) lunMapEntries.get(0);
if (null == igDetails.get(1) || null == lunMapEntries.get(2)) {
_log.warn("IG Name or hlu is null in returned lun map response for volume {}", igVolume.toString());
continue;
}
String igNameToProcess = (String) igDetails.get(1);
if (!igName.equalsIgnoreCase(igNameToProcess)) {
continue;
}
Double hluNumber = (Double) lunMapEntries.get(2);
_log.info("Found HLU {} for volume {}", hluNumber, igVolume.getVolInfo().get(1));
// for each IG involved, the same volume is visible through different HLUs.
// TODO we might need a list of HLU for each Volume URI
discoveredVolumes.put(BlockObject.normalizeWWN(igVolume.getWwn()), Integer.valueOf(hluNumber.intValue()));
}
}
igNameToVolMap.put(igName, discoveredVolumes);
}
// update each mask
for (Entry<URI, Set<String>> entry : maskToIGNameMap.entrySet()) {
URI maskId = entry.getKey();
ExportMask exportMask = dbClient.queryObject(ExportMask.class, maskId);
if (exportMask == null || exportMask.getInactive()) {
continue;
}
Map<String, Integer> discoveredVolumes = new HashMap<String, Integer>();
for (String igName : entry.getValue()) {
discoveredVolumes.putAll(igNameToVolMap.get(igName));
}
// Clear the existing volumes to update with the latest info
if (exportMask.getExistingVolumes() != null && !exportMask.getExistingVolumes().isEmpty()) {
exportMask.getExistingVolumes().clear();
}
//COP-27296 fix
if (null == exportMask.getUserAddedVolumes()) {
exportMask.setUserAddedVolumes(new StringMap());
}
// We need to look at all related initiators from the affected EM. We can use this list
// to then find all related volumes across all EMs. This will allow us to properly
// perform our validations.
List<Initiator> relatedInitiators = new ArrayList<Initiator>();
if (exportMask.getInitiators() != null && !exportMask.getInitiators().isEmpty()) {
Collection<URI> relatedInitiatorURIs = Collections2.transform(exportMask.getInitiators(),
CommonTransformerFunctions.FCTN_STRING_TO_URI);
relatedInitiators.addAll(dbClient.queryObject(Initiator.class, relatedInitiatorURIs));
}
Set<URI> allRelatedVolumes = new HashSet<URI>();
allRelatedVolumes.addAll(findAllRelatedExportMaskVolumesForInitiator(relatedInitiators, exportMask.getStorageDevice()));
// If there are related volumes found, get the WWNs so we can diff against what has
// been discovered on the array.
Set<String> allRelatedVolumesWWN = new HashSet<String>();
for (URI relatedVolumeURI : allRelatedVolumes) {
BlockObject relatedObj = BlockObject.fetch(dbClient, relatedVolumeURI);
if (relatedObj != null) {
allRelatedVolumesWWN.add(relatedObj.getWWN());
}
}
Set<String> existingVolumes = Sets.difference(discoveredVolumes.keySet(), allRelatedVolumesWWN);
_log.info(String.format("XtremIO discovered volumes: {%s}%n", Joiner.on(',').join(discoveredVolumes.keySet())));
_log.info(String.format("%nXtremIO mask existing volumes: {%s}%n", Joiner.on(',').join(existingVolumes)));
for (String wwn : existingVolumes) {
exportMask.addToExistingVolumesIfAbsent(wwn, discoveredVolumes.get(wwn).toString());
}
// Update user added volume's HLU information in ExportMask and ExportGroup
ExportMaskUtils.updateHLUsInExportMask(exportMask, discoveredVolumes, dbClient);
dbClient.updateObject(exportMask);
if (mask != null && maskId.equals(mask.getId())) {
maskToReturn = exportMask;
}
}
} catch (Exception e) {
if (null != e.getMessage() && !e.getMessage().contains(XtremIOConstants.OBJECT_NOT_FOUND)) {
String msg = String.format("Error when refreshing export masks for the system %s, details: %s", storage.forDisplay(),
e.getMessage());
throw XtremIOApiException.exceptions.refreshExistingMaskFailure(msg, e);
} else {
_log.warn("Error refreshing export masks for the system {}", storage.forDisplay());
}
}
return maskToReturn;
}
private void runLunMapDeletionAlgorithm(StorageSystem storage, ExportMask exportMask,
List<URI> volumes, List<Initiator> initiators, TaskCompleter taskCompleter)
throws DeviceControllerException {
// find LunMap associated with Volume
// Then find initiatorGroup associated with this lun map
// find initiators associated with IG, if the given list is of initiators is same, then run
// removeLunMap
XtremIOClient client = null;
// if host Name is not available in at least one of the initiator, then set it to
// Default_IG;
try {
String hostName = null;
String clusterName = null;
client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
for (Initiator initiator : initiators) {
if (null != initiator.getHostName()) {
// initiators already grouped by Host
hostName = initiator.getHostName();
clusterName = initiator.getClusterName();
break;
}
}
ArrayListMultimap<String, Initiator> groupInitiatorsByIG = XtremIOProvUtils.mapInitiatorToInitiatorGroup(
storage.getSerialNumber(), initiators,
null, xioClusterName, client);
ExportMaskValidationContext ctx = new ExportMaskValidationContext();
ctx.setStorage(storage);
ctx.setExportMask(exportMask);
ctx.setInitiators(initiators);
ctx.setAllowExceptions(!WorkflowService.getInstance().isStepInRollbackState(taskCompleter.getOpId()));
XtremIOExportMaskInitiatorsValidator initiatorsValidator = (XtremIOExportMaskInitiatorsValidator) validator
.removeVolumes(ctx);
initiatorsValidator.setInitiatorToIGMap(groupInitiatorsByIG);
initiatorsValidator.validate();
Set<String> igNames = groupInitiatorsByIG.keySet();
List<String> failedVolumes = new ArrayList<String>();
for (URI volumeUri : volumes) {
BlockObject blockObj = BlockObject.fetch(dbClient, volumeUri);
_log.info("Block Obj {} , wwn {}", blockObj.getId(), blockObj.getWWN());
XtremIOVolume xtremIOVolume = null;
if (URIUtil.isType(volumeUri, Volume.class)) {
xtremIOVolume = XtremIOProvUtils.isVolumeAvailableInArray(client,
blockObj.getDeviceLabel(), xioClusterName);
// It could be that the block object is actually a snapshot on the array
// because a VPLEX volume was created on top of a snapshot, and when this is
// done, a dummy backend volume is created using the data from the block
// snapshot because VPLEX volumes need to be built on volumes. So, if the
// returned value is null, check the snapshots.
if (xtremIOVolume == null) {
xtremIOVolume = XtremIOProvUtils.isSnapAvailableInArray(client,
blockObj.getDeviceLabel(), xioClusterName);
}
} else {
xtremIOVolume = XtremIOProvUtils.isSnapAvailableInArray(client,
blockObj.getDeviceLabel(), xioClusterName);
}
if (null != xtremIOVolume) {
// I need lun map id and igName
// if iGName is available in the above group, then remove lunMap
_log.info("Volume Details {}", xtremIOVolume.toString());
_log.info("Volume lunMap details {}", xtremIOVolume.getLunMaps().toString());
Set<String> lunMaps = new HashSet<String>();
String volId = xtremIOVolume.getVolInfo().get(2);
if (xtremIOVolume.getLunMaps().isEmpty()) {
// handle scenarios where volumes gets unexported already
_log.info("Volume {} doesn't have any existing export available on Array, unexported already.",
xtremIOVolume.toString());
exportMask.removeFromUserCreatedVolumes(blockObj);
exportMask.removeVolume(blockObj.getId());
continue;
}
for (List<Object> lunMapEntries : xtremIOVolume.getLunMaps()) {
@SuppressWarnings("unchecked")
List<Object> igDetails = (List<Object>) lunMapEntries.get(0);
String igName = (String) igDetails.get(1);
// Ig details is actually transforming to A double by deofault, even though
// its modeled as List<String>
// hence this logic
Double IgIdDouble = (Double) igDetails.get(2);
String igId = String.valueOf(IgIdDouble.intValue());
_log.info("IG Name: {} Id: {} found in Lun Map", igName, igId);
if (!igNames.contains(igName)) {
_log.info(
"Volume is associated with IG {} which is not in the removal list requested, ignoring..",
igName);
continue;
}
@SuppressWarnings("unchecked")
List<Object> tgtGroupDetails = (List<Object>) lunMapEntries.get(1);
Double tgIdDouble = (Double) tgtGroupDetails.get(2);
String tgtid = String.valueOf(tgIdDouble.intValue());
String lunMapId = volId.concat(XtremIOConstants.UNDERSCORE).concat(igId)
.concat(XtremIOConstants.UNDERSCORE).concat(tgtid);
_log.info("LunMap Id {} Found associated with Volume {}", lunMapId,
blockObj.getDeviceLabel());
lunMaps.add(lunMapId);
}
// deletion of lun Maps
// there will be only one lun map always
for (String lunMap : lunMaps) {
try {
client.deleteLunMap(lunMap, xioClusterName);
} catch (Exception e) {
failedVolumes.add(volumeUri.toString().concat(XtremIOConstants.DASH).concat(e.getMessage()));
_log.warn("Deletion of Lun Map {} failed}", lunMap, e);
}
}
} else {
exportMask.removeFromUserCreatedVolumes(blockObj);
exportMask.removeVolume(blockObj.getId());
}
}
dbClient.updateObject(exportMask);
if (!failedVolumes.isEmpty()) {
String errMsg = "Export Operations failed for these volumes: ".concat(Joiner.on(", ").join(
failedVolumes));
ServiceError serviceError = DeviceControllerException.errors.jobFailedMsg(
errMsg, null);
taskCompleter.error(dbClient, serviceError);
return;
}
// Clean IGs if empty
deleteInitiatorGroup(groupInitiatorsByIG, client, xioClusterName);
// delete IG Folder as well if IGs are empty
deleteInitiatorGroupFolder(client, xioClusterName, clusterName, hostName, storage);
taskCompleter.ready(dbClient);
} catch (Exception e) {
_log.error(String.format("Export Operations failed - maskName: %s", exportMask.getId()
.toString()), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(dbClient, serviceError);
}
}
/**
* It deletes the LunMap if the IG contains no other initiators than the requested ones.
* Else it removes the requested initiators from the IG
*/
private void runLunMapDeletionOrRemoveInitiatorAlgorithm(StorageSystem storage, ExportMask exportMask,
List<URI> volumes, List<Initiator> initiators, TaskCompleter taskCompleter)
throws DeviceControllerException {
// find LunMap associated with Volume
// Then find initiatorGroup associated with this lun map
XtremIOClient client = null;
// if host Name is not available in at least one of the initiator, then set it to
// Default_IG;
try {
String hostName = null;
String clusterName = null;
client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory);
String xioClusterName = client.getClusterDetails(storage.getSerialNumber()).getName();
boolean initiatorsOfRP = ExportUtils.checkIfInitiatorsForRP(initiators);
for (Initiator initiator : initiators) {
if (null != initiator.getHostName()) {
// initiators already grouped by Host
hostName = initiator.getHostName();
clusterName = initiator.getClusterName();
break;
}
}
ArrayListMultimap<String, Initiator> groupInitiatorsByIG = XtremIOProvUtils.mapInitiatorToInitiatorGroup(
storage.getSerialNumber(), initiators,
null, xioClusterName, client);
ArrayListMultimap<String, Initiator> knownInitiatorsToIGMap = ArrayListMultimap.create();
// DU validations for removing volumes from IG.
ExportMaskValidationContext ctx = new ExportMaskValidationContext();
ctx.setStorage(storage);
ctx.setExportMask(exportMask);
ctx.setInitiators(initiators);
ctx.setAllowExceptions(!WorkflowService.getInstance().isStepInRollbackState(taskCompleter.getOpId()));
XtremIOExportMaskInitiatorsValidator initiatorsValidator = (XtremIOExportMaskInitiatorsValidator) validator
.removeVolumes(ctx);
initiatorsValidator.setInitiatorToIGMap(groupInitiatorsByIG);
initiatorsValidator.setKnownInitiatorToIGMap(knownInitiatorsToIGMap);
initiatorsValidator.validate();
Set<String> igNames = groupInitiatorsByIG.keySet();
List<String> failedVolumes = new ArrayList<String>();
List<String> failedIGs = new ArrayList<String>();
for (URI volumeUri : volumes) {
BlockObject blockObj = BlockObject.fetch(dbClient, volumeUri);
_log.info("Block Obj {} , wwn {}", blockObj.getId(), blockObj.getWWN());
XtremIOVolume xtremIOVolume = null;
if (URIUtil.isType(volumeUri, Volume.class)) {
xtremIOVolume = XtremIOProvUtils.isVolumeAvailableInArray(client,
blockObj.getDeviceLabel(), xioClusterName);
} else {
if (URIUtil.isType(volumeUri, BlockSnapshot.class) && BlockObject.checkForRP(dbClient, volumeUri)) {
// If the BlockObject is a BlockSnapshot of type RP (bookmark), there will be no exported
// snapshot. In this case, a target volume will have been exported and the deviceLabel of
// the BlockSnapshot reflects the name of that target.
_log.info(String.format(
"Dealing with a RecoverPoint bookmark lun mapping. Checking to see if volume %s is available on array.",
blockObj.getDeviceLabel()));
xtremIOVolume = XtremIOProvUtils.isVolumeAvailableInArray(client,
blockObj.getDeviceLabel(), xioClusterName);
} else {
xtremIOVolume = XtremIOProvUtils.isSnapAvailableInArray(client,
blockObj.getDeviceLabel(), xioClusterName);
}
}
if (null != xtremIOVolume) {
// I need lun map id and igName
// if iGName is available in the above group:
_log.info("Volume Details {}", xtremIOVolume.toString());
_log.info("Volume lunMap details {}", xtremIOVolume.getLunMaps().toString());
// Lun Maps to delete
Set<String> lunMaps = new HashSet<String>();
boolean removeInitiator = false;
String volId = xtremIOVolume.getVolInfo().get(2);
if (xtremIOVolume.getLunMaps().isEmpty()) {
// handle scenarios where volumes gets unexported already
_log.info("Volume {} doesn't have any existing export available on Array, unexported already.",
xtremIOVolume.toString());
exportMask.removeFromUserCreatedVolumes(blockObj);
exportMask.removeVolume(blockObj.getId());
continue;
}
for (List<Object> lunMapEntries : xtremIOVolume.getLunMaps()) {
@SuppressWarnings("unchecked")
List<Object> igDetails = (List<Object>) lunMapEntries.get(0);
String igName = (String) igDetails.get(1);
// IG details is actually transforming to a double by default, even though
// its modeled as List<String>
// hence this logic
Double IgIdDouble = (Double) igDetails.get(2);
String igId = String.valueOf(IgIdDouble.intValue());
_log.info("IG Name: {} Id: {} found in Lun Map", igName, igId);
if (!igNames.contains(igName)) {
_log.info(
"Volume is associated with IG {} which is not in the removal list requested, ignoring..",
igName);
continue;
}
/**
* i) If Cluster export:
* If there are additional initiators other than the requested ones (Single IG with all cluster
* initiators)
* - - - remove initiator from IG,
* - - - Note: If initiators are of RP (CTRL-13622), always delete LunMap.
* - ii) Host export:
* - - -- delete LunMap
*/
boolean igHasOtherHostInitiatorsOfSameCluster = knownInitiatorsToIGMap.get(igName).size() > groupInitiatorsByIG
.get(igName).size();
if (!initiatorsOfRP && clusterName != null && igHasOtherHostInitiatorsOfSameCluster) {
removeInitiator = true;
}
if (!removeInitiator) {
// delete LunMap
@SuppressWarnings("unchecked")
List<Object> tgtGroupDetails = (List<Object>) lunMapEntries.get(1);
Double tgIdDouble = (Double) tgtGroupDetails.get(2);
String tgtid = String.valueOf(tgIdDouble.intValue());
String lunMapId = volId.concat(XtremIOConstants.UNDERSCORE).concat(igId)
.concat(XtremIOConstants.UNDERSCORE).concat(tgtid);
_log.info("LunMap Id {} Found associated with Volume {}", lunMapId,
blockObj.getDeviceLabel());
lunMaps.add(lunMapId);
}
}
// deletion of lun Maps
// there will be only one lun map always
for (String lunMap : lunMaps) {
try {
client.deleteLunMap(lunMap, xioClusterName);
} catch (Exception e) {
failedVolumes.add(volumeUri.toString().concat(XtremIOConstants.DASH).concat(e.getMessage()));
_log.warn("Deletion of Lun Map {} failed}", lunMap, e);
}
}
// remove initiator from IG
if (removeInitiator) {
_log.info("Removing requested intiators from IG instead of deleting LunMap"
+ " as the IG contains other Host's initiators belonging to same Cluster.");
ctx = new ExportMaskValidationContext();
ctx.setStorage(storage);
ctx.setExportMask(exportMask);
ctx.setBlockObjects(volumes, dbClient);
ctx.setAllowExceptions(!WorkflowService.getInstance().isStepInRollbackState(taskCompleter.getOpId()));
// DU validation when removing initiators
XtremIOExportMaskVolumesValidator volumeValidator = (XtremIOExportMaskVolumesValidator) validator
.removeInitiators(ctx);
volumeValidator.setIgNames(groupInitiatorsByIG.keySet());
volumeValidator.validate();
List<Initiator> initiatorsToBeRemoved = new ArrayList<Initiator>();
// Get the context from the task completer, in case this is a rollback.
ExportOperationContext context = (ExportOperationContext) WorkflowService.getInstance()
.loadStepData(taskCompleter.getOpId());
if (context != null && context.getOperations() != null) {
ListIterator li = context.getOperations().listIterator(context.getOperations().size());
while (li.hasPrevious()) {
_log.info("Handling deleteExportMask as a result of rollback");
ExportOperationContextOperation operation = (ExportOperationContextOperation) li.previous();
if (operation != null
&& XtremIOExportOperationContext.OPERATION_ADD_INITIATORS_TO_INITIATOR_GROUP
.equals(operation.getOperation())) {
initiatorsToBeRemoved = (List<Initiator>) operation.getArgs().get(0);
_log.info("Removing initiators {} as part of rollback", Joiner.on(',').join(initiatorsToBeRemoved));
}
}
} else {
initiatorsToBeRemoved = initiators;
}
// Deleting the initiator automatically removes the initiator from lun map
for (Initiator initiator : initiatorsToBeRemoved) {
try {
// check if Initiator has already been deleted during previous volume processing
String initiatorName = initiator.getMappedInitiatorName(storage.getSerialNumber());
XtremIOInitiator initiatorObj = client.getInitiator(initiatorName, xioClusterName);
if (null != initiatorObj) {
client.deleteInitiator(initiatorName, xioClusterName);
} else {
_log.info("Initiator {} already deleted", initiatorName);
}
} catch (Exception e) {
failedIGs.add(initiator.getLabel().concat(XtremIOConstants.DASH).concat(e.getMessage()));
_log.warn("Removal of Initiator {} from IG failed", initiator.getLabel(), e);
}
}
}
} else {
exportMask.removeFromUserCreatedVolumes(blockObj);
exportMask.removeVolume(blockObj.getId());
}
}
dbClient.updateObject(exportMask);
if (!failedVolumes.isEmpty()) {
String errMsg = "Export Operations failed for these volumes: ".concat(Joiner.on(", ").join(
failedVolumes));
ServiceError serviceError = DeviceControllerException.errors.jobFailedMsg(
errMsg, null);
taskCompleter.error(dbClient, serviceError);
return;
}
if (!failedIGs.isEmpty()) {
String errMsg = "Export Operations failed deleting these initiators: ".concat(Joiner.on(", ").join(
failedIGs));
ServiceError serviceError = DeviceControllerException.errors.jobFailedMsg(errMsg, null);
taskCompleter.error(dbClient, serviceError);
return;
}
// Clean IGs if empty
deleteInitiatorGroup(groupInitiatorsByIG, client, xioClusterName);
// delete IG Folder as well if IGs are empty
deleteInitiatorGroupFolder(client, xioClusterName, clusterName, hostName, storage);
taskCompleter.ready(dbClient);
} catch (Exception e) {
_log.error(String.format("Export Operations failed - maskName: %s", exportMask.getId()
.toString()), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(dbClient, serviceError);
}
}
private String getInitiatorGroupFolderName(String clusterName, String hostName, StorageSystem storage) {
String igFolderName = "";
if (clusterName != null && !clusterName.isEmpty()) {
// cluster
DataSource dataSource = dataSourceFactory.createXtremIOClusterInitiatorGroupFolderNameDataSource(
clusterName, storage);
igFolderName = customConfigHandler.getComputedCustomConfigValue(
CustomConfigConstants.XTREMIO_CLUSTER_INITIATOR_GROUP_FOLDER_NAME, storage.getSystemType(), dataSource);
} else {
DataSource dataSource = dataSourceFactory.createXtremIOHostInitiatorGroupFolderNameDataSource(
hostName, storage);
igFolderName = customConfigHandler.getComputedCustomConfigValue(
CustomConfigConstants.XTREMIO_HOST_INITIATOR_GROUP_FOLDER_NAME, storage.getSystemType(), dataSource);
}
return igFolderName;
}
private void addInitiatorToInitiatorGroup(XtremIOClient client, String xioClusterName,
String clusterName, String hostName, List<Initiator> initiatorsToBeCreated,
Set<String> igNames, ExportMask exportMask, StorageSystem storage, TaskCompleter taskCompleter)
throws Exception {
XtremIOInitiatorGroup igGroup = null;
// create initiator group folder and initiator group
String igFolderName = getInitiatorGroupFolderName(clusterName, hostName, storage);
if (null == client.getTagDetails(igFolderName, XTREMIO_ENTITY_TYPE.InitiatorGroup.name(), xioClusterName)) {
_log.info("Creating IG Folder with name {}", igFolderName);
client.createTag(igFolderName, null, XtremIOConstants.XTREMIO_ENTITY_TYPE.InitiatorGroup.name(), xioClusterName);
}
DataSource dataSource = dataSourceFactory.createXtremIOInitiatorGroupNameDataSource(
hostName, storage);
String igName = customConfigHandler.getComputedCustomConfigValue(
CustomConfigConstants.XTREMIO_INITIATOR_GROUP_NAME, storage.getSystemType(), dataSource);
igGroup = client.getInitiatorGroup(igName, xioClusterName);
if (null == igGroup) {
// create a new IG
_log.info("Creating Initiator Group with name {}", igName);
client.createInitiatorGroup(igName, igFolderName, xioClusterName);
ExportOperationContext.insertContextOperation(taskCompleter, XtremIOExportOperationContext.OPERATION_CREATE_INITIATOR_GROUP,
igName);
igGroup = client.getInitiatorGroup(igName, xioClusterName);
if (null == igGroup) {
_log.info("Neither IG is already present nor able to create on Array {}", hostName);
} else {
_log.info("Created Initiator Group {} with # initiators {}", igGroup.getName(),
igGroup.getNumberOfInitiators());
igNames.add(igGroup.getName());
}
} else {
igNames.add(igGroup.getName());
_log.info("Found Initiator Group {} with # initiators {}", igGroup.getName(),
igGroup.getNumberOfInitiators());
}
// add all the left out initiators to this folder
for (Initiator remainingInitiator : initiatorsToBeCreated) {
_log.info("Initiator {} Label {} ", remainingInitiator.getInitiatorPort(),
remainingInitiator.getLabel());
String initiatorName = ((null == remainingInitiator.getLabel() || remainingInitiator
.getLabel().isEmpty()) ? remainingInitiator.getInitiatorPort()
: remainingInitiator.getLabel());
List<Initiator> createdInitiators = new ArrayList<Initiator>();
_log.info("Initiator {} ", initiatorName);
try {
String os = null;
if (client.isVersion2() && !NullColumnValueGetter.isNullURI(remainingInitiator.getHost())) {
Host host = dbClient.queryObject(Host.class, remainingInitiator.getHost());
os = XtremIOProvUtils.getInitiatorHostOS(host);
}
// create initiator
client.createInitiator(initiatorName, igGroup.getName(),
remainingInitiator.getInitiatorPort(), os, xioClusterName);
createdInitiators.add(remainingInitiator);
remainingInitiator.setLabel(initiatorName);
remainingInitiator.mapInitiatorName(storage.getSerialNumber(), initiatorName);
dbClient.updateObject(remainingInitiator);
} catch (Exception e) {
// assume initiator already part of another group look for
// port_address_not_unique
// CTRL-5956 - Few Initiators cannot be registered on XtremIO Array, throw exception even if one
// initiator registration
// fails.
_log.warn("Initiator {} already available or not able to register the same on Array. Rediscover the Array and try again.",
remainingInitiator.getInitiatorPort());
throw e;
} finally {
ExportOperationContext.insertContextOperation(taskCompleter,
XtremIOExportOperationContext.OPERATION_ADD_INITIATORS_TO_INITIATOR_GROUP,
createdInitiators);
}
}
}
private void runLunMapCreationAlgorithm(StorageSystem storage, ExportMask exportMask, VolumeURIHLU[] volumeURIHLUs,
List<Initiator> initiators, List<URI> targets, XtremIOClient client, String xioClusterName,
ArrayListMultimap<String, Initiator> initiatorToIGMap, List<Initiator> initiatorsToBeCreated, TaskCompleter taskCompleter)
throws DeviceControllerException {
Set<String> igNames = null;
List<URI> mappedVolumes = new ArrayList<URI>();
// if host Name is not available in at least one of the initiator, then set it to
// Default_IG;
try {
String hostName = null;
String clusterName = null;
_log.info("Finding re-usable IGs available on Array {}", storage.getNativeGuid());
List<URI> initiatorIds = new ArrayList<>();
for (Initiator initiator : initiators) {
initiatorIds.add(initiator.getId());
if (null != initiator.getHostName()) {
// initiators already grouped by Host
hostName = initiator.getHostName();
clusterName = initiator.getClusterName();
break;
}
}
// since we're reusing existing IGs, volumes might get exposed to other initiators in
// IG.
// for initiators without label, create IGs and add them to a host folder by name
// initiator.getHost() in cases ,where initiator.getLabel() is not present, we try
// creating IG, if fails
// then we consider that it's already created
igNames = new HashSet<String>();
igNames.addAll(initiatorToIGMap.keySet());
if (initiatorsToBeCreated != null && !initiatorsToBeCreated.isEmpty()) {
// create new initiator and add to IG; add IG to IG folder
addInitiatorToInitiatorGroup(client, xioClusterName, clusterName, hostName,
initiatorsToBeCreated, igNames, exportMask, storage, taskCompleter);
List<URI> volumeURIs = new ArrayList<URI>();
for (VolumeURIHLU volURIHLU : volumeURIHLUs) {
volumeURIs.add(volURIHLU.getVolumeURI());
}
XtremIOExportMaskVolumesValidator volumeValidator = (XtremIOExportMaskVolumesValidator) validator.addInitiators(storage,
exportMask, volumeURIs);
volumeValidator.setIgNames(initiatorToIGMap.keySet());
volumeValidator.validate();
}
if (igNames.isEmpty()) {
ServiceError serviceError = DeviceControllerException.errors.xtremioInitiatorGroupsNotDetected(storage.getNativeGuid());
taskCompleter.error(dbClient, serviceError);
return;
}
// Small block of declarations/initializations to assist with vblock boot volume check in below block.
Set<String> vblockConflictIgNames = new HashSet<>();
Map<URI, Integer> volumeMap = new HashMap<>();
for (VolumeURIHLU volumeIdHlu : volumeURIHLUs) {
volumeMap.put(volumeIdHlu.getVolumeURI(), Integer.valueOf(volumeIdHlu.getHLU()));
}
// Check for volumes which are already mapped to the IG. We do not need to create lun map for them.
ArrayListMultimap<String, String> volumesToIGMap = ArrayListMultimap.create();
for (String igName : igNames) {
List<XtremIOVolume> igVols = XtremIOProvUtils.getInitiatorGroupVolumes(igName, xioClusterName, client);
for (XtremIOVolume igVolume : igVols) {
volumesToIGMap.put(igName, igVolume.getVolInfo().get(1));
/**
* COP-28674: During Vblock Boot volume export, if existing masking views are found then check for existing volumes
* If found throw exception. This condition is valid only for boot volume vblock export.
*/
if (ExportMaskUtils.isVblockHost(initiatorIds, dbClient) && ExportMaskUtils.isBootVolume(dbClient, volumeMap)) {
_log.error(String.format("VBlock boot volume Export: found existing IG (%s) with volumes in it on %s.", igName,
storage.getNativeGuid()));
vblockConflictIgNames.add(igName);
}
}
}
// If we collected any conflicting IG names (because we're about to export a boot volume to a vblock host, and
// there are additional volumes also visible to any of the initiators associated with that host), post a verbose
// error for the customer.
if (!vblockConflictIgNames.isEmpty()) {
ServiceError serviceError = DeviceControllerException.errors.existingMaskFoundDuringBootVolumeExportXio(
Joiner.on(',').join(vblockConflictIgNames), hostName);
taskCompleter.error(dbClient, serviceError);
return;
}
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_052);
// create Lun Maps
for (VolumeURIHLU volURIHLU : volumeURIHLUs) {
BlockObject blockObj = BlockObject.fetch(dbClient, volURIHLU.getVolumeURI());
String hluValue = volURIHLU.getHLU().equalsIgnoreCase(ExportGroup.LUN_UNASSIGNED_STR) ? "-1" : volURIHLU.getHLU();
_log.info("HLU value {}", hluValue);
for (String igName : igNames) {
List<String> igVols = volumesToIGMap.get(igName);
if (igVols != null && !igVols.contains(blockObj.getDeviceLabel())) {
// Create lun map
_log.info("Creating Lun Map for Volume {} using IG {}", blockObj.getDeviceLabel(), igName);
client.createLunMap(blockObj.getDeviceLabel(), igName, hluValue, xioClusterName);
mappedVolumes.add(blockObj.getId());
}
}
}
// post process created lun maps
for (VolumeURIHLU volURIHLU : volumeURIHLUs) {
BlockObject blockObj = BlockObject.fetch(dbClient, volURIHLU.getVolumeURI());
Integer hluNumberFound = 0;
// get volume/snap details again and populate wwn and hlu
XtremIOVolume xtremIOVolume = null;
String deviceName = blockObj.getDeviceLabel();
xtremIOVolume = XtremIOProvUtils.isVolumeAvailableInArray(client, deviceName, xioClusterName);
// COP-19828: If we can't find a volume by the given name, try to find a snap with the given name
if (xtremIOVolume == null) {
xtremIOVolume = XtremIOProvUtils.isSnapAvailableInArray(client, deviceName, xioClusterName);
}
if (xtremIOVolume != null) {
_log.info("Volume lunMap details Found {}", xtremIOVolume.getLunMaps().toString());
if (!xtremIOVolume.getWwn().isEmpty()) {
blockObj.setWWN(xtremIOVolume.getWwn());
blockObj.setNativeId(xtremIOVolume.getWwn());
dbClient.updateObject(blockObj);
}
for (String igName : igNames) {
for (List<Object> lunMapEntries : xtremIOVolume.getLunMaps()) {
@SuppressWarnings("unchecked")
// This can't be null
List<Object> igDetails = (List<Object>) lunMapEntries.get(0);
if (null == igDetails.get(1) || null == lunMapEntries.get(2)) {
_log.warn("IG Name is null in returned lun map response for volume {}", xtremIOVolume.toString());
continue;
}
String igNameToProcess = (String) igDetails.get(1);
_log.info("IG Name: {} found in Lun Map", igNameToProcess);
if (!igName.equalsIgnoreCase(igNameToProcess)) {
_log.info(
"Volume is associated with IG {} which is not in the expected list requested, ignoring..",
igNameToProcess);
continue;
}
Double hluNumber = (Double) lunMapEntries.get(2);
_log.info("Found HLU {} for volume {}", hluNumber, blockObj.getDeviceLabel());
// for each IG involved, the same volume is visible thro different HLUs.
// TODO we might need a list of HLU for each Volume URI
hluNumberFound = hluNumber.intValue();
exportMask.addVolume(blockObj.getId(), hluNumberFound);
exportMask.addToUserCreatedVolumes(blockObj);
}
}
}
}
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_053);
ExportOperationContext.insertContextOperation(taskCompleter,
XtremIOExportOperationContext.OPERATION_ADD_VOLUMES_TO_INITIATOR_GROUP,
mappedVolumes);
_log.info("Updated Volumes with HLUs {} after successful export",
Joiner.on(",").join(exportMask.getVolumes().entrySet()));
dbClient.updateObject(exportMask);
// Test mechanism to invoke a failure. No-op on production systems.
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_003);
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_002);
taskCompleter.ready(dbClient);
} catch (Exception e) {
_log.error(String.format("Export Operations failed - maskName: %s", exportMask.getId()
.toString()), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(dbClient, serviceError);
}
}
private void deleteInitiatorGroupFolder(XtremIOClient client, String xioClusterName, String clusterName, String hostName,
StorageSystem system) throws Exception {
String tempIGFolderName = getInitiatorGroupFolderName(clusterName, hostName, system);
XtremIOTag igFolder = client.getTagDetails(tempIGFolderName, XTREMIO_ENTITY_TYPE.InitiatorGroup.name(), xioClusterName);
if (null != igFolder && "0".equalsIgnoreCase(igFolder.getNumberOfDirectObjs())) {
try {
_log.info("# of IGs {} in Folder {}", igFolder.getNumberOfDirectObjs(), clusterName);
client.deleteTag(tempIGFolderName, XtremIOConstants.XTREMIO_ENTITY_TYPE.InitiatorGroup.name(), xioClusterName);
} catch (Exception e) {
if (null != e.getMessage() && !e.getMessage().contains(XtremIOConstants.OBJECT_NOT_FOUND)) {
_log.warn("Deleting Initatiator Group Folder {} failed with exception {}", tempIGFolderName, e.getMessage());
throw e;
} else {
_log.warn("Initatiator Group Folder {} not found. Might be already deleted.", tempIGFolderName);
}
}
}
}
private void deleteInitiatorGroup(ArrayListMultimap<String, Initiator> groupInitiatorsByIG,
XtremIOClient client, String xioClusterName) throws Exception {
for (Entry<String, Collection<Initiator>> entry : groupInitiatorsByIG.asMap().entrySet()) {
String igName = entry.getKey();
try {
// find # initiators for this IG
XtremIOInitiatorGroup ig = client.getInitiatorGroup(igName, xioClusterName);
if (ig != null) {
int numberOfVolumes = Integer.parseInt(ig.getNumberOfVolumes());
_log.info("Initiator Group {} left with Volume size {}", igName, numberOfVolumes);
if (numberOfVolumes == 0) {
// delete Initiator Group
client.deleteInitiatorGroup(igName, xioClusterName);
// remove export mask from export groip
} else {
_log.info("Skipping IG Group {} deletion", igName);
}
}
} catch (Exception e) {
if (null != e.getMessage() && !e.getMessage().contains(XtremIOConstants.OBJECT_NOT_FOUND)) {
_log.warn("Deleting Initatiator Group {} failed with exception {}", igName, e.getMessage());
throw e;
} else {
_log.warn("Initatiator Group {} not found. Might be already deleted.", igName);
}
}
}
}
/**
* This method provides an easy way to get all the related export mask volumes for an initiator so that
* validation can be done properly for the XIO IG.
*
* The main export mask doesn't necessarily tell us all the volumes that are present on the XIO IG. Just the
* volumes exposed for that single mask.
*
* To find that out if there are more volumes exposed to the XIO IG we need a common initiator to find
* if there are any other export masks that have other user added volumes present. If there are, collect
* all those related volumes so that they can be used for validation.
*
* @param initiators The initiator in question
* @param storageSystemURI URI of the storage system to focus on
* @return A set of related volumes, empty set if none are found.
*/
private Set<URI> findAllRelatedExportMaskVolumesForInitiator(List<Initiator> initiators, URI storageSystemURI) {
Set<URI> volumes = new HashSet<URI>();
if (initiators != null && storageSystemURI != null) {
for (Initiator init : initiators) {
// Get all export masks that contain the initiator
URIQueryResultList relatedExportMasks = new URIQueryResultList();
dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getExportMaskInitiatorConstraint(init.getId().toString()),
relatedExportMasks);
for (URI relatedExportMaskURI : relatedExportMasks) {
// The related export mask contains the initiator in question based on the initial query constraint,
// so if the related export mask is NOT the current export mask and is for the same storage device
// then we need to collect the user added volumes.
ExportMask relatedExportMask = dbClient.queryObject(ExportMask.class, relatedExportMaskURI);
if (relatedExportMask != null
&& storageSystemURI.equals(relatedExportMask.getStorageDevice())) {
if (relatedExportMask.getUserAddedVolumes() != null) {
// Get the user added volumes from the related export mask and transform them into URIs
Collection<URI> vols = Collections2.transform(relatedExportMask.getUserAddedVolumes().values(),
CommonTransformerFunctions.FCTN_STRING_TO_URI);
// Keep track of the user added volumes
volumes.addAll(vols);
}
}
}
}
}
if (!CollectionUtils.isEmpty(volumes)) {
_log.info(String.format("Volumes across related export masks for XIO "
+ "[%s] using initiators [%s] - volumes: [%s]",
storageSystemURI,
Joiner.on(',').join(initiators),
Joiner.on(',').join(volumes)));
}
return volumes;
}
@Override
public void updateStorageGroupPolicyAndLimits(StorageSystem storage, ExportMask exportMask,
List<URI> volumeURIs, VirtualPool newVirtualPool, boolean rollback,
TaskCompleter taskCompleter) throws Exception {
}
@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();
}
}