/* * 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.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.emc.storageos.customconfigcontroller.CustomConfigConstants; import com.emc.storageos.customconfigcontroller.DataSource; import com.emc.storageos.customconfigcontroller.DataSourceFactory; import com.emc.storageos.customconfigcontroller.impl.CustomConfigHandler; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.StoragePool; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.Volume.PersonalityTypes; import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils; import com.emc.storageos.db.client.util.NameGenerator; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.exceptions.DeviceControllerErrors; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper; import com.emc.storageos.svcs.errorhandling.model.ServiceError; import com.emc.storageos.util.InvokeTestFailure; import com.emc.storageos.volumecontroller.DefaultBlockStorageDevice; import com.emc.storageos.volumecontroller.TaskCompleter; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator; import com.emc.storageos.volumecontroller.impl.VolumeURIHLU; import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper; import com.emc.storageos.volumecontroller.impl.xtremio.prov.utils.XtremIOProvUtils; import com.emc.storageos.xtremio.restapi.XtremIOClient; import com.emc.storageos.xtremio.restapi.XtremIOClientFactory; 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.XtremIOConsistencyGroup; import com.emc.storageos.xtremio.restapi.model.response.XtremIOVolume; import com.google.common.collect.Lists; public class XtremIOStorageDevice extends DefaultBlockStorageDevice { private static final Logger _log = LoggerFactory.getLogger(XtremIOStorageDevice.class); // Max retries for RecoverPoint volume delete operation private static final int MAX_RP_RETRIES = 30; // Wait 30 seconds before attempting another call to delete the RecoverPoint associated XIO volume private static final int RP_WAIT_FOR_RETRY = 30000; XtremIOClientFactory xtremioRestClientFactory; DbClient dbClient; @Autowired private DataSourceFactory dataSourceFactory; @Autowired private CustomConfigHandler customConfigHandler; private XtremIOExportOperations xtremioExportOperationHelper; private XtremIOSnapshotOperations snapshotOperations; private NameGenerator _nameGenerator; private static final String noOpOnThisStorageArrayString = "No operation to perform on this storage array..."; public void setXtremioExportOperationHelper(XtremIOExportOperations xtremioExportOperationHelper) { this.xtremioExportOperationHelper = xtremioExportOperationHelper; } public void setSnapshotOperations(XtremIOSnapshotOperations snapshotOperations) { this.snapshotOperations = snapshotOperations; } public void setXtremioRestClientFactory(XtremIOClientFactory xtremioRestClientFactory) { this.xtremioRestClientFactory = xtremioRestClientFactory; } public void setDbClient(DbClient dbClient) { this.dbClient = dbClient; } public void setNameGenerator(final NameGenerator nameGenerator) { _nameGenerator = nameGenerator; } @Override public void doCreateVolumes(StorageSystem storage, StoragePool storagePool, String opId, List<Volume> volumes, VirtualPoolCapabilityValuesWrapper capabilities, TaskCompleter taskCompleter) throws DeviceControllerException { Map<String, String> failedVolumes = new HashMap<String, String>(); XtremIOClient client = null; try { client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); BlockConsistencyGroup cgObj = null; boolean isCG = false; Volume vol = volumes.get(0); // If the volume is regular volume and in CG if (!NullColumnValueGetter.isNullURI(vol.getConsistencyGroup())) { cgObj = dbClient.queryObject(BlockConsistencyGroup.class, vol.getConsistencyGroup()); if (cgObj != null && cgObj.created(storage.getId())) { // Only set this flag to true if the CG reference is valid // and it is already created on the storage system. isCG = true; //RP back-end volumes DO NOT have personality flag set. All RP volumes will satisfy checkForRP //Find out out if this is a RP volume that is not a back-end volume to a RP+VPLEX volume. boolean excludeRPNotBackendVolumes = vol.checkForRp() && (NullColumnValueGetter.isNotNullValue(vol.getPersonality())); // If Vplex backed volume does not have replicationGroupInstance value, should not add the volume into back-end cg // Only XIO volumes that are back-end volumes to RP+VPLEX volumes will be in an array CG if arrayConsistency is chosen. if (excludeRPNotBackendVolumes || (Volume.checkForVplexBackEndVolume(dbClient, vol) && NullColumnValueGetter.isNullValue(vol.getReplicationGroupInstance()))) { isCG = false; } } } // find the project this volume belongs to. URI projectUri = volumes.get(0).getProject().getURI(); String clusterName = client.getClusterDetails(storage.getSerialNumber()).getName(); // For version 1 APIs, find the volume folder corresponding to this project // if not found ,create new folder, else reuse this folder and add volume to it. // For version 2 APIs, find tags corresponding to this project // if not found ,create new tag, else reuse this tag and tag the volume. boolean isVersion2 = client.isVersion2(); String volumesFolderName = ""; if (isVersion2) { volumesFolderName = XtremIOProvUtils.createTagsForVolumeAndSnaps(client, getVolumeFolderName(projectUri, storage), clusterName).get(XtremIOConstants.VOLUME_KEY); } else { volumesFolderName = XtremIOProvUtils.createFoldersForVolumeAndSnaps(client, getVolumeFolderName(projectUri, storage)) .get(XtremIOConstants.VOLUME_KEY); } Random randomNumber = new Random(); for (Volume volume : volumes) { try { XtremIOVolume createdVolume = null; String userDefinedLabel = _nameGenerator.generate("", volume.getLabel(), "", '_', XtremIOConstants.XTREMIO_MAX_VOL_LENGTH); volume.setLabel(userDefinedLabel); while (null != XtremIOProvUtils.isVolumeAvailableInArray(client, volume.getLabel(), clusterName)) { _log.info("Volume with name {} already exists", volume.getLabel()); String tempLabel = userDefinedLabel.concat("_").concat( String.valueOf(randomNumber.nextInt(1000))); volume.setLabel(tempLabel); _log.info("Retrying volume creation with label {}", tempLabel); } // If the volume is a recoverpoint protected volume, the capacity has already been // adjusted in the RPBlockServiceApiImpl class therefore there is no need to adjust it here. // If it is not, add 1 MB extra to make up the missing bytes due to divide by 1024 (Usecase: XIO on HA side of VPLEX Distributed) // If there are no additional bytes requested, don't add that extra 1 MB. int amountToAdjustCapacity = 1; if (Volume.checkForProtectedVplexBackendVolume(dbClient, volume) || volume.checkForRp() || ((volume.getCapacity().doubleValue() / (1024 * 1024)) % 1) == 0) { amountToAdjustCapacity = 0; } Long capacityInMB = new Long(volume.getCapacity() / (1024 * 1024) + amountToAdjustCapacity); String capacityInMBStr = String.valueOf(capacityInMB).concat("m"); _log.info("Sending create volume request with name: {}, size: {}", volume.getLabel(), capacityInMBStr); client.createVolume(volume.getLabel(), capacityInMBStr, volumesFolderName, clusterName); createdVolume = client.getVolumeDetails(volume.getLabel(), clusterName); _log.info("Created volume details {}", createdVolume.toString()); // For version 2, tag the created volume if (isVersion2) { client.tagObject(volumesFolderName, XTREMIO_ENTITY_TYPE.Volume.name(), volume.getLabel(), clusterName); // Do not add RP+VPlex journal or target backing volumes to consistency groups. // This causes issues with local array snapshots of RP+VPlex volumes. String cgName = volume.getReplicationGroupInstance(); if (isCG && !RPHelper.isAssociatedToRpVplexType(volume, dbClient, PersonalityTypes.METADATA, PersonalityTypes.TARGET) && NullColumnValueGetter.isNotNullValue(cgName)) { client.addVolumeToConsistencyGroup(volume.getLabel(), cgName, clusterName); } } volume.setNativeId(createdVolume.getVolInfo().get(0)); volume.setWWN(createdVolume.getVolInfo().get(0)); volume.setDeviceLabel(volume.getLabel()); volume.setAccessState(Volume.VolumeAccessState.READWRITE.name()); // When a volume is created, the WWN field will be empty, hence use the volume's native Id as WWN // If the REST API wwn field is populated, then use it. if (!createdVolume.getWwn().isEmpty()) { volume.setWWN(createdVolume.getWwn()); } String nativeGuid = NativeGUIDGenerator.generateNativeGuid(dbClient, volume); volume.setNativeGuid(nativeGuid); volume.setProvisionedCapacity(Long.parseLong(createdVolume .getAllocatedCapacity()) * 1024); volume.setAllocatedCapacity(Long.parseLong(createdVolume.getAllocatedCapacity()) * 1024); dbClient.updateAndReindexObject(volume); } catch (Exception e) { failedVolumes.put(volume.getLabel(), ControllerUtils.getMessage(e)); _log.error("Error during volume create.", e); } } if (!failedVolumes.isEmpty()) { StringBuffer errMsg = new StringBuffer("Failed to create volumes: "); for (String failedVolume : failedVolumes.keySet()) { errMsg.append(failedVolume).append(":").append(failedVolumes.get(failedVolume)); } ServiceError error = DeviceControllerErrors.xtremio.createVolumeFailure(errMsg.toString()); taskCompleter.error(dbClient, error); } else { taskCompleter.ready(dbClient); } } catch (Exception e) { _log.error("Error while creating volumes", e); ServiceError error = DeviceControllerErrors.xtremio.createVolumeFailure(e.getMessage()); taskCompleter.error(dbClient, error); } // update StoragePool capacity try { XtremIOProvUtils.updateStoragePoolCapacity(client, dbClient, storagePool); } catch (Exception e) { _log.warn("Error while updating pool capacity", e); } } @Override public void doExpandVolume(StorageSystem storage, StoragePool pool, Volume volume, Long sizeInBytes, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("Expand Volume..... Started"); try { XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); String clusterName = client.getClusterDetails(storage.getSerialNumber()).getName(); Long sizeInGB = new Long(sizeInBytes / (1024 * 1024 * 1024)); // XtremIO Rest API supports only expansion in GBs. String capacityInGBStr = String.valueOf(sizeInGB).concat("g"); client.expandVolume(volume.getDeviceLabel(), capacityInGBStr, clusterName); XtremIOVolume createdVolume = client.getVolumeDetails(volume.getDeviceLabel(), clusterName); volume.setProvisionedCapacity(Long.parseLong(createdVolume .getAllocatedCapacity()) * 1024); volume.setAllocatedCapacity(Long.parseLong(createdVolume.getAllocatedCapacity()) * 1024); volume.setCapacity(Long.parseLong(createdVolume.getAllocatedCapacity()) * 1024); dbClient.updateObject(volume); // update StoragePool capacity try { XtremIOProvUtils.updateStoragePoolCapacity(client, dbClient, pool); } catch (Exception e) { _log.warn("Error while updating pool capacity", e); } taskCompleter.ready(dbClient); _log.info("Expand Volume..... End"); } catch (Exception e) { _log.error("Error while expanding volumes", e); ServiceError error = DeviceControllerErrors.xtremio.expandVolumeFailure(e); taskCompleter.error(dbClient, error); } } @Override public void doDeleteVolumes(StorageSystem storageSystem, String opId, List<Volume> volumes, TaskCompleter completer) throws DeviceControllerException { Map<String, String> failedVolumes = new HashMap<String, String>(); try { XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storageSystem, xtremioRestClientFactory); String clusterName = client.getClusterDetails(storageSystem.getSerialNumber()).getName(); URI projectUri = volumes.get(0).getProject().getURI(); URI poolUri = volumes.get(0).getPool(); for (Volume volume : volumes) { try { if (null != XtremIOProvUtils.isVolumeAvailableInArray(client, volume.getLabel(), clusterName)) { // If the volume is regular volume & in CG // i.e. it's not RP or a backend volume for a RP+VPLEX Target or Journal if (client.isVersion2() && volume.getConsistencyGroup() != null && NullColumnValueGetter.isNotNullValue(volume.getReplicationGroupInstance()) && !RPHelper.isAssociatedToRpVplexType(volume, dbClient, PersonalityTypes.METADATA, PersonalityTypes.TARGET)) { BlockConsistencyGroup consistencyGroupObj = dbClient.queryObject(BlockConsistencyGroup.class, volume.getConsistencyGroup()); String cgName = volume.getReplicationGroupInstance(); XtremIOConsistencyGroup xioCG = XtremIOProvUtils.isCGAvailableInArray(client, cgName, clusterName); // Check if CG has volumes if (null != xioCG && null != xioCG.getVolList() && !xioCG.getVolList().isEmpty()) { boolean isVolRemovedFromCG = false; // Verify if the volumes is part of the CG or not. If Exists always remove from CG if (checkIfVolumeExistsInCG(xioCG.getVolList(), volume)) { _log.info("Removing volume {} from consistency group {}", volume.getDeviceLabel(), cgName); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_038); client.removeVolumeFromConsistencyGroup(volume.getDeviceLabel(), cgName, clusterName); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_039); isVolRemovedFromCG = true; } else { _log.info("Volume {} doesn't exist on CG {}", volume.getDeviceLabel(), cgName); } // Perform remove CG only when we removed the volume from CG. if (isVolRemovedFromCG) { // Query the CG to reflect the latest data on array. xioCG = XtremIOProvUtils.isCGAvailableInArray(client, cgName, clusterName); if (null == xioCG.getVolList() || xioCG.getVolList().isEmpty()) { InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_038); client.removeConsistencyGroup(cgName, clusterName); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_039); _log.info("CG is empty on array. Remove array association from the CG"); consistencyGroupObj.removeSystemConsistencyGroup(storageSystem.getId().toString(), cgName); // clear the LOCAL type StringSet types = consistencyGroupObj.getTypes(); if (types != null) { types.remove(Types.LOCAL.name()); consistencyGroupObj.setTypes(types); } dbClient.updateObject(consistencyGroupObj); } } } } // If this is a RecoverPoint volume (straight RP protection XIO volume or XIO VPlex // backing volume). if (volume.checkForRp() || RPHelper.isAssociatedToAnyRpVplexTypes(volume, dbClient)) { int attempt = 0; while (attempt++ <= MAX_RP_RETRIES) { try { _log.info(String.format("Deleting RecoverPoint volume %s (attempt %s/%s)", volume.getDeviceLabel(), attempt, MAX_RP_RETRIES)); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_040); client.deleteVolume(volume.getDeviceLabel(), clusterName); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_041); break; } catch (XtremIOApiException e) { // RP/XIO source/target volumes are placed in consistency groups generated by XIO and RP, not ViPR. // There is occasionally some lag or delay in the communication between RP and XIO when a replication // set is removed. This results in the XIO volume not being removed from its CG before the delete // call is invoked which throws an exception. We isolate this exception below so we can wait and // retry the operation in hopes the proper communication has taken place between RP and XIO and the // volume removed from the consistency group. if (attempt != MAX_RP_RETRIES && e.getMessage().contains("cannot_remove_volume_that_is_in_consistency_group")) { _log.warn(String .format("Encountered exception attempting delete RP volume %s. Waiting %s milliseconds before trying again. Error: %s", volume.getLabel(), RP_WAIT_FOR_RETRY, e.getMessage())); try { Thread.sleep(RP_WAIT_FOR_RETRY); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); } } else { // re-throw the exception if this is not the one we care about throw e; } } } } else { _log.info("Deleting the volume {}", volume.getDeviceLabel()); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_040); client.deleteVolume(volume.getDeviceLabel(), clusterName); InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_041); } } } catch (Exception e) { _log.error("Error during volume {} delete.", volume.getDeviceLabel(), e); failedVolumes.put(volume.getDeviceLabel(), ControllerUtils.getMessage(e)); } } if (!failedVolumes.isEmpty()) { StringBuffer errMsg = new StringBuffer("Failed to delete volumes: "); for (String failedVolume : failedVolumes.keySet()) { errMsg.append(failedVolume).append(":").append(failedVolumes.get(failedVolume)); } ServiceError error = DeviceControllerErrors.xtremio.deleteVolumeFailure(errMsg.toString()); completer.error(dbClient, error); } else { String volumeFolderName = getVolumeFolderName(projectUri, storageSystem); XtremIOProvUtils.cleanupVolumeFoldersIfNeeded(client, clusterName, volumeFolderName, storageSystem); completer.ready(dbClient); } // update StoragePool capacity for pools changed StoragePool pool = dbClient.queryObject(StoragePool.class, poolUri); try { _log.info("Updating Pool {} Capacity", pool.getNativeGuid()); XtremIOProvUtils.updateStoragePoolCapacity(client, dbClient, pool); } catch (Exception e) { _log.warn("Error while updating pool capacity for pool {} ", poolUri, e); } } catch (Exception e) { _log.error("Error while deleting volumes", e); ServiceError error = DeviceControllerErrors.xtremio.deleteVolumeFailure(e.getMessage()); completer.error(dbClient, error); } } /** * Verify whether the given volume exists in the CG Volume list or not. * If Exists return true, else false. * * @param volList - CG Volume List * @param volume - Volume to check. * @return true if the volume Exists in CG * false if the volume not found in CG. */ private boolean checkIfVolumeExistsInCG(List<List<Object>> volList, Volume volume) { for (List<Object> vols : volList) { if (null != vols.get(1)) { // vols contains 3 volume related elements. The second element is the device // name, which we will use to match against the provided volume's label. String cgVolLabel = vols.get(1).toString(); if (cgVolLabel.equalsIgnoreCase(volume.getDeviceLabel())) { return true; } } } return false; } @Override public void doExportCreate(StorageSystem storage, ExportMask exportMask, Map<URI, Integer> volumeMap, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { /** * - Starting point: List of ViPR Initiator Records updated via Discovery - Find initiator * using Initiator label. - If not found, POST to IG and use IG-name to POST to Initiator * with IG (IG per Host) - If found, find IG - Maintain an in-memory Set of IGs - Lookup * Volumes - Create LUNMap foreach (IG, V) * * 0. Discover Initiators and update the corresponding labels if not set. 0a. If label is * not matching with user given, then keep a local data structure in memory and use it only * during this process. 1. Look up IG by name and find IGs 2. Store IGs in list 3. If list * empty, then create a new IG-folder with host or cluster Name 4. Create initiators add * them to a new IG, and add them to IG-folder 5. If list partial, 5a . Then check whether * existing IGs is having a complete subset of expected initiators of the same host 5b. If * yes, then add the remaining initiators to the any one of the initiator Group which * matches above criteria 5c. Else, create a new initiator group and add the remaining * initiators. 6. If complete, no action 7. Create LunMaps for each Volume and IG. * * */ _log.info("{} doExportCreate START ...", storage.getSerialNumber()); VolumeURIHLU[] volumeLunArray = ControllerUtils.getVolumeURIHLUArray( storage.getSystemType(), volumeMap, dbClient); xtremioExportOperationHelper.createExportMask(storage, exportMask.getId(), volumeLunArray, targets, initiators, taskCompleter); _log.info("{} doExportCreate END ...", storage.getSerialNumber()); } @Override public void doExportDelete(StorageSystem storage, ExportMask exportMask, List<URI> volumeURIs, List<URI> initiatorURIs, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportDelete START ...", storage.getSerialNumber()); List<Initiator> initiators = Lists.newArrayList(); if (initiatorURIs != null) { initiators.addAll(dbClient.queryObject(Initiator.class, initiatorURIs)); } xtremioExportOperationHelper.deleteExportMask(storage, exportMask.getId(), volumeURIs, new ArrayList<URI>(), initiators, taskCompleter); _log.info("{} doExportDelete END ...", storage.getSerialNumber()); } @Override public void doExportAddVolume(StorageSystem storage, ExportMask exportMask, URI volume, Integer lun, List<Initiator> initiators, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportAddVolume START ...", storage.getSerialNumber()); Map<URI, Integer> map = new HashMap<URI, Integer>(); map.put(volume, lun); VolumeURIHLU[] volumeLunArray = ControllerUtils.getVolumeURIHLUArray( storage.getSystemType(), map, dbClient); xtremioExportOperationHelper.addVolumes(storage, exportMask.getId(), volumeLunArray, initiators, taskCompleter); _log.info("{} doExportAddVolume END ...", storage.getSerialNumber()); } @Override public void doExportAddVolumes(StorageSystem storage, ExportMask exportMask, List<Initiator> initiators, Map<URI, Integer> volumes, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportAddVolumes START ...", storage.getSerialNumber()); VolumeURIHLU[] volumeLunArray = ControllerUtils.getVolumeURIHLUArray( storage.getSystemType(), volumes, dbClient); xtremioExportOperationHelper.addVolumes(storage, exportMask.getId(), volumeLunArray, initiators, taskCompleter); _log.info("{} doExportAddVolumes END ...", storage.getSerialNumber()); } @Override public void doExportRemoveVolume(StorageSystem storage, ExportMask exportMask, URI volume, List<Initiator> initiators, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportRemoveVolumes START ...", storage.getSerialNumber()); List<URI> volumeUris = new ArrayList<URI>(); volumeUris.add(volume); xtremioExportOperationHelper.removeVolumes(storage, exportMask.getId(), volumeUris, initiators, taskCompleter); _log.info("{} doExportRemoveVolumes END ...", storage.getSerialNumber()); } @Override public void doExportRemoveVolumes(StorageSystem storage, ExportMask exportMask, List<URI> volumes, List<Initiator> initiators, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportRemoveVolumes START ...", storage.getSerialNumber()); xtremioExportOperationHelper.removeVolumes(storage, exportMask.getId(), volumes, initiators, taskCompleter); _log.info("{} doExportRemoveVolumes END ...", storage.getSerialNumber()); } @Override public void doExportAddInitiator(StorageSystem storage, ExportMask exportMask, List<URI> volumeURIs, Initiator initiator, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportAddInitiator START ...", storage.getSerialNumber()); List<Initiator> initiatorList = new ArrayList<Initiator>(); initiatorList.add(initiator); xtremioExportOperationHelper.addInitiators(storage, exportMask.getId(), volumeURIs, initiatorList, targets, taskCompleter); _log.info("{} doExportAddInitiators END ...", storage.getSerialNumber()); } @Override public void doExportAddInitiators(StorageSystem storage, ExportMask exportMask, List<URI> volumeURIs, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportAddInitiators START ...", storage.getSerialNumber()); xtremioExportOperationHelper.addInitiators(storage, exportMask.getId(), volumeURIs, initiators, targets, taskCompleter); _log.info("{} doExportAddInitiators END ...", storage.getSerialNumber()); } @Override public void doExportRemoveInitiator(StorageSystem storage, ExportMask exportMask, List<URI> volumes, Initiator initiator, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportRemoveInitiator START ...", storage.getSerialNumber()); List<Initiator> initiatorList = new ArrayList<Initiator>(); initiatorList.add(initiator); xtremioExportOperationHelper.removeInitiators(storage, exportMask.getId(), volumes, initiatorList, targets, taskCompleter); _log.info("{} doExportRemoveInitiator END ...", storage.getSerialNumber()); } @Override public void doExportRemoveInitiators(StorageSystem storage, ExportMask exportMask, List<URI> volumes, List<Initiator> initiators, List<URI> targets, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doExportRemoveInitiators START ...", storage.getSerialNumber()); xtremioExportOperationHelper.removeInitiators(storage, exportMask.getId(), volumes, initiators, targets, taskCompleter); _log.info("{} doExportRemoveInitiators END ...", storage.getSerialNumber()); } @Override public void doCreateSnapshot(StorageSystem storage, List<URI> snapshotList, Boolean createInactive, Boolean readOnly, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("SnapShot Creation..... Started"); List<BlockSnapshot> snapshots = dbClient.queryObject(BlockSnapshot.class, snapshotList); XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); if (client.isVersion2() && ControllerUtils.checkSnapshotsInConsistencyGroup(snapshots, dbClient, taskCompleter)) { snapshotOperations.createGroupSnapshots(storage, snapshotList, createInactive, readOnly, taskCompleter); } else { for (URI snapshotURI : snapshotList) { snapshotOperations.createSingleVolumeSnapshot(storage, snapshotURI, createInactive, readOnly, taskCompleter); } } _log.info("SnapShot Creation..... End"); } @Override public void doDeleteSnapshot(StorageSystem storage, URI snapshot, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("SnapShot Deletion..... Started"); List<BlockSnapshot> snapshots = dbClient.queryObject(BlockSnapshot.class, Arrays.asList(snapshot)); XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); if (client.isVersion2() && ControllerUtils.checkSnapshotsInConsistencyGroup(snapshots, dbClient, taskCompleter)) { snapshotOperations.deleteGroupSnapshots(storage, snapshot, taskCompleter); } else { snapshotOperations.deleteSingleVolumeSnapshot(storage, snapshot, taskCompleter); } _log.info("SnapShot Deletion..... End"); } @Override public void doRestoreFromSnapshot(StorageSystem storage, URI volume, URI snapshot, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("SnapShot Restore..... Started"); List<BlockSnapshot> snapshots = dbClient.queryObject(BlockSnapshot.class, Arrays.asList(snapshot)); XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); if (client.isVersion2() && ControllerUtils.checkSnapshotsInConsistencyGroup(snapshots, dbClient, taskCompleter)) { snapshotOperations.restoreGroupSnapshots(storage, volume, snapshot, taskCompleter); } else { snapshotOperations.restoreSingleVolumeSnapshot(storage, volume, snapshot, taskCompleter); } _log.info("SnapShot Restore..... End"); } @Override public void doResyncSnapshot(StorageSystem storage, URI volume, URI snapshot, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("SnapShot resync..... Started"); List<BlockSnapshot> snapshots = dbClient.queryObject(BlockSnapshot.class, Arrays.asList(snapshot)); XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); if (client.isVersion2() && ControllerUtils.checkSnapshotsInConsistencyGroup(snapshots, dbClient, taskCompleter)) { snapshotOperations.resyncGroupSnapshots(storage, volume, snapshot, taskCompleter); } else { snapshotOperations.resyncSingleVolumeSnapshot(storage, volume, snapshot, taskCompleter); } _log.info("SnapShot resync..... End"); } @Override public void doDeleteConsistencyGroup(StorageSystem storage, final URI consistencyGroupId, String replicationGroupName, Boolean keepRGName, Boolean markInactive, final TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doDeleteConsistencyGroup START ...", storage.getSerialNumber()); ServiceError serviceError = null; try { // Check if the consistency group exists BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupId); URI systemURI = storage.getId(); if (consistencyGroup == null || consistencyGroup.getInactive()) { _log.info(String.format("%s is inactive or deleted", consistencyGroupId)); return; } String groupName = replicationGroupName != null ? replicationGroupName : consistencyGroup.getLabel(); // This will be null, if consistencyGroup references no system CG's for storage. if (groupName == null) { _log.info(String.format("%s contains no system CG for %s. Assuming it has already been deleted.", consistencyGroupId, systemURI)); // Clean up the system consistency group references BlockConsistencyGroupUtils.cleanUpCGAndUpdate(consistencyGroup, storage.getId(), groupName, markInactive, dbClient); return; } XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); // We still need throw exception for standard CG. if (!client.isVersion2() && consistencyGroup.isProtectedCG()) { StringSet cgTypes = consistencyGroup.getTypes(); cgTypes.remove(BlockConsistencyGroup.Types.LOCAL.name()); consistencyGroup.setTypes(cgTypes); _log.info("{} Operation deleteConsistencyGroup not supported for the xtremio array version"); } else { String clusterName = client.getClusterDetails(storage.getSerialNumber()).getName(); Project cgProject = dbClient.queryObject(Project.class, consistencyGroup.getProject()); if (null != XtremIOProvUtils.isCGAvailableInArray(client, groupName, clusterName)) { client.removeConsistencyGroup(groupName, clusterName); } if (null != XtremIOProvUtils.isTagAvailableInArray(client, cgProject.getLabel(), XtremIOConstants.XTREMIO_ENTITY_TYPE.ConsistencyGroup.name(), clusterName)) { client.deleteTag(cgProject.getLabel(), XtremIOConstants.XTREMIO_ENTITY_TYPE.ConsistencyGroup.name(), clusterName); } if (keepRGName) { return; } // Clean up the system consistency group references BlockConsistencyGroupUtils.cleanUpCG(consistencyGroup, storage.getId(), groupName, markInactive, dbClient); } dbClient.updateObject(consistencyGroup); _log.info("{} doDeleteConsistencyGroup END ...", storage.getSerialNumber()); } catch (Exception e) { _log.error(String.format("Delete Consistency Group operation failed %s", e)); serviceError = DeviceControllerException.errors.jobFailed(e); } finally { if (serviceError != null) { taskCompleter.error(dbClient, serviceError); } else { taskCompleter.ready(dbClient); } } } @Override public void doCreateConsistencyGroup(StorageSystem storage, URI consistencyGroupId, String replicationGroupName, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doCreateConsistencyGroup START ...", storage.getSerialNumber()); try { XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupId); boolean isXioV2 = client.isVersion2(); if (!isXioV2 && consistencyGroup.isProtectedCG()) { _log.info("{} Operation createConsistencyGroup not supported for the xtremio array version"); } else { String clusterName = client.getClusterDetails(storage.getSerialNumber()).getName(); Project cgProject = dbClient.queryObject(Project.class, consistencyGroup.getProject()); String cgName = replicationGroupName != null ? replicationGroupName : consistencyGroup.getLabel(); client.createConsistencyGroup(cgName, clusterName); String cgTagName = XtremIOProvUtils.createTagsForConsistencyGroup(client, cgProject.getLabel(), clusterName); consistencyGroup.addSystemConsistencyGroup(storage.getId().toString(), cgName); consistencyGroup.addConsistencyGroupTypes(Types.LOCAL.name()); client.tagObject(cgTagName, XTREMIO_ENTITY_TYPE.ConsistencyGroup.name(), cgName, clusterName); if (!consistencyGroup.isProtectedCG() && NullColumnValueGetter.isNullURI(consistencyGroup.getStorageController())) { consistencyGroup.setStorageController(storage.getId()); } } dbClient.updateObject(consistencyGroup); taskCompleter.ready(dbClient); _log.info("{} doCreateConsistencyGroup END ...", storage.getSerialNumber()); } catch (Exception e) { _log.error(String.format("Create Consistency Group operation failed %s", e)); ServiceError serviceError = DeviceControllerException.errors.jobFailed(e); taskCompleter.error(dbClient, serviceError); } } @Override public void doAddToConsistencyGroup(StorageSystem storage, URI consistencyGroupId, String replicationGroupName, List<URI> blockObjects, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doAddToConsistencyGroup START ...", storage.getSerialNumber()); BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupId); try { // Check if the consistency group exists XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); if (!client.isVersion2()) { _log.info("Nothing to add to consistency group {}", consistencyGroup.getLabel()); taskCompleter.ready(dbClient); return; } String clusterName = client.getClusterDetails(storage.getSerialNumber()).getName(); String cgName = replicationGroupName != null ? replicationGroupName : consistencyGroup.getLabel(); XtremIOConsistencyGroup cg = XtremIOProvUtils.isCGAvailableInArray(client, cgName, clusterName); if (cg == null) { _log.error("The consistency group does not exist in the array: {}", storage.getSerialNumber()); taskCompleter.error(dbClient, DeviceControllerException.exceptions .consistencyGroupNotFound(consistencyGroup.getLabel(), consistencyGroup.getCgNameOnStorageSystem(storage.getId()))); return; } List<BlockObject> updatedBlockObjects = new ArrayList<BlockObject>(); for (URI uri : blockObjects) { BlockObject blockObject = BlockObject.fetch(dbClient, uri); if (blockObject != null) { if (blockObject.getClass().isInstance(Volume.class)) { Volume volume = (Volume) blockObject; if (volume.checkForRp() || RPHelper.isAssociatedToRpVplexType(volume, dbClient, PersonalityTypes.METADATA, PersonalityTypes.TARGET)) { // Do not add RP+VPlex journal or target backing volumes to consistency groups. // This causes issues with local array snapshots of RP+VPlex volumes. continue; } } client.addVolumeToConsistencyGroup(blockObject.getLabel(), cgName, clusterName); blockObject.setConsistencyGroup(consistencyGroupId); updatedBlockObjects.add(blockObject); } } dbClient.updateAndReindexObject(updatedBlockObjects); taskCompleter.ready(dbClient); _log.info("{} doAddToConsistencyGroup END ...", storage.getSerialNumber()); } catch (Exception e) { _log.error(String.format("Add To Consistency Group operation failed %s", e)); // Remove any references to the consistency group for (URI blockObjectURI : blockObjects) { BlockObject blockObject = BlockObject.fetch(dbClient, blockObjectURI); if (blockObject != null) { blockObject.setConsistencyGroup(NullColumnValueGetter.getNullURI()); } dbClient.persistObject(blockObject); } taskCompleter.error(dbClient, DeviceControllerException.exceptions .failedToAddMembersToConsistencyGroup(consistencyGroup.getLabel(), consistencyGroup.getLabel(), e.getMessage())); } } @Override public void doRemoveFromConsistencyGroup(StorageSystem storage, URI consistencyGroupId, List<URI> blockObjects, TaskCompleter taskCompleter) throws DeviceControllerException { _log.info("{} doRemoveFromConsistencyGroup START ...", storage.getSerialNumber()); BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupId); String groupName = null; try { // get the group name from one of the block objects; we expect all of them to be the same group Iterator<URI> itr = blockObjects.iterator(); while (itr.hasNext()) { BlockObject blockObject = BlockObject.fetch(dbClient, itr.next()); if (blockObject != null && !blockObject.getInactive() && !NullColumnValueGetter.isNullValue(blockObject.getReplicationGroupInstance())) { groupName = blockObject.getReplicationGroupInstance(); break; } } // Check if the replication group exists if (groupName != null) { // Check if the consistency group exists XtremIOClient client = XtremIOProvUtils.getXtremIOClient(dbClient, storage, xtremioRestClientFactory); if (!client.isVersion2()) { _log.info("Nothing to remove from consistency group {}", consistencyGroup.getLabel()); taskCompleter.ready(dbClient); return; } String clusterName = client.getClusterDetails(storage.getSerialNumber()).getName(); XtremIOConsistencyGroup cg = XtremIOProvUtils.isCGAvailableInArray(client, groupName, clusterName); if (cg == null) { _log.error("The consistency group does not exist in the array: {}", storage.getSerialNumber()); taskCompleter.error(dbClient, DeviceControllerException.exceptions .consistencyGroupNotFound(groupName, consistencyGroup.getCgNameOnStorageSystem(storage.getId()))); return; } for (URI uri : blockObjects) { BlockObject blockObject = BlockObject.fetch(dbClient, uri); if (blockObject != null) { client.removeVolumeFromConsistencyGroup(blockObject.getLabel(), blockObject.getReplicationGroupInstance(), clusterName); blockObject.setConsistencyGroup(NullColumnValueGetter.getNullURI()); blockObject.setReplicationGroupInstance(NullColumnValueGetter.getNullStr()); dbClient.updateObject(blockObject); } } } taskCompleter.ready(dbClient); _log.info("{} doRemoveFromConsistencyGroup END ...", storage.getSerialNumber()); } catch (Exception e) { _log.error(String.format("Remove from Consistency Group operation failed %s", e)); taskCompleter.error(dbClient, DeviceControllerException.exceptions .failedToRemoveMembersToConsistencyGroup(consistencyGroup.getLabel(), consistencyGroup.getLabel(), e.getMessage())); } } @Override public void doDeleteConsistencyGroup(StorageSystem storage, final URI consistencyGroupId, String replicationGroupName, Boolean keepRGName, Boolean markInactive, String sourceReplicationGroup, final TaskCompleter taskCompleter) throws DeviceControllerException { doDeleteConsistencyGroup(storage, consistencyGroupId, replicationGroupName, keepRGName, markInactive, taskCompleter); } @Override public Map<String, Set<URI>> findExportMasks(StorageSystem storage, List<String> initiatorNames, boolean mustHaveAllPorts) throws DeviceControllerException { return new HashMap<String, Set<URI>>(); } @Override public Set<Integer> findHLUsForInitiators(StorageSystem storage, List<String> initiatorNames, boolean mustHaveAllPorts) { return xtremioExportOperationHelper.findHLUsForInitiators(storage, initiatorNames, mustHaveAllPorts); } @Override public ExportMask refreshExportMask(StorageSystem storage, ExportMask mask) throws DeviceControllerException { return xtremioExportOperationHelper.refreshExportMask(storage, mask); } @Override public boolean validateStorageProviderConnection(String ipAddress, Integer portNumber) { return true; } private String getVolumeFolderName(URI projectURI, StorageSystem storage) { String volumeGroupFolderName = ""; Project project = dbClient.queryObject(Project.class, projectURI); DataSource dataSource = dataSourceFactory.createXtremIOVolumeFolderNameDataSource(project, storage); volumeGroupFolderName = customConfigHandler.getComputedCustomConfigValue( CustomConfigConstants.XTREMIO_VOLUME_FOLDER_NAME, storage.getSystemType(), dataSource); return volumeGroupFolderName; } }