/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.vplex.api; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.StringTokenizer; import org.codehaus.jettison.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.vplex.api.VPlexCacheStatusInfo.InvalidateStatus; import com.emc.storageos.vplex.api.VPlexVirtualVolumeInfo.WaitOnRebuildResult; import com.emc.storageos.vplex.api.clientdata.VolumeInfo; import com.sun.jersey.api.client.ClientResponse; /** * VPlexApiVirtualVolumeManager provides methods creating and destroying virtual * volumes. */ public class VPlexApiVirtualVolumeManager { // Logger reference. private static Logger s_logger = LoggerFactory.getLogger(VPlexApiVirtualVolumeManager.class); // A reference to the API client. private final VPlexApiClient _vplexApiClient; /** * Package protected constructor. * * @param client A reference to the API client. */ VPlexApiVirtualVolumeManager(VPlexApiClient client) { _vplexApiClient = client; } /** * Creates a VPlex virtual volume using the passed native volume * information. The passed native volume information should identify a * single volume on a backend storage array connected to a VPlex cluster * when a simple, non-distributed virtual volume is desired. When a * distributed virtual volume is desired the native volume info should * identify two volumes. One volume should reside on a backend storage array * connected to one VPlex cluster in a VPlex metro configuration. The other * volumes should reside on a backend storage array connected to the other * VPlex cluster in a VPlex Metro configuration. * * NOTE: Currently, backend volumes newly exported to the VPlex must be * discovered prior to creating a virtual volume using them by invoking the * rediscoverStorageSystems API. * * @param nativeVolumeInfoList The native volume information. * @param isDistributed true for a distributed virtual volume, false * otherwise. * @param discoveryRequired true if the passed native volumes are newly * exported and need to be discovered by the VPlex. * @param preserveData true if the native volume data should be preserved * during virtual volume creation. * @param winningClusterId Used to set detach rules for distributed volumes. * @param clusterInfoList A list of VPlexClusterInfo specifying the info for the VPlex * clusters. * @param findVirtualVolume If true findVirtualVolume method is called after virtual volume is created. * @param thinEnabled If true, the virtual volume should be created as a thin-enabled virtual volume. * @param clusterName The clusterName the volume is on. if non-null, backend volume * search will be restricted to the named cluster. * * @return The information for the created virtual volume. * * @throws VPlexApiException When an error occurs creating the virtual * volume. */ VPlexVirtualVolumeInfo createVirtualVolume(List<VolumeInfo> nativeVolumeInfoList, boolean isDistributed, boolean discoveryRequired, boolean preserveData, String winningClusterId, List<VPlexClusterInfo> clusterInfoList, boolean findVirtualVolume, boolean thinEnabled, String clusterName) throws VPlexApiException { s_logger.info("Request to create {} virtual volume.", (isDistributed ? "distributed" : "local")); if ((isDistributed) && (nativeVolumeInfoList.size() != 2)) { throw VPlexApiException.exceptions.twoDevicesRequiredForDistVolume(); } else if ((!isDistributed) && (nativeVolumeInfoList.size() != 1)) { throw VPlexApiException.exceptions.oneDevicesRequiredForLocalVolume(); } if (null == clusterInfoList) { clusterInfoList = new ArrayList<VPlexClusterInfo>(); } // Find the storage volumes corresponding to the passed native // volume information, discover them if required. Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap = findStorageVolumes(nativeVolumeInfoList, discoveryRequired, clusterInfoList, clusterName); // For a distributed virtual volume, verify logging volumes // have been configured on each cluster. if (isDistributed) { for (VPlexClusterInfo clusterInfo : clusterInfoList) { if (!clusterInfo.hasLoggingVolume()) { throw VPlexApiException.exceptions.clusterHasNoLoggingVolumes(clusterInfo.getName()); } } s_logger.info("Verified logging volumes"); } // Claim the storage volumes claimStorageVolumes(storageVolumeInfoMap, preserveData); s_logger.info("Claimed storage volumes"); // Try and build up the VPLEX artifacts from the claimed storage // volumes and create the new virtual volume. If we get an error, // clean up the VPLEX artifacts and unclaim the storage volumes. try { // Create extents List<VPlexStorageVolumeInfo> storageVolumeInfoList = new ArrayList<VPlexStorageVolumeInfo>(); for (VolumeInfo nativeVolumeInfo : nativeVolumeInfoList) { storageVolumeInfoList.add(storageVolumeInfoMap.get(nativeVolumeInfo)); } createExtents(storageVolumeInfoList); s_logger.info("Created extents on storage volumes"); // Find the extents just created and create local devices on // those extents. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); List<VPlexExtentInfo> extentInfoList = discoveryMgr.findExtents(storageVolumeInfoList); createLocalDevices(extentInfoList); s_logger.info("Created local devices on extents"); // Find the local devices just created. If the virtual volume is // to be distributed, first create a distributed device from the // local devices, then create a virtual volumes from the distributed // device. Otherwise, create a virtual volume from the local device. String clusterId; String deviceName; String devicePath; List<VPlexDeviceInfo> localDevices = discoveryMgr.findLocalDevices(extentInfoList); if (isDistributed) { // Create and find the distributed device using the local devices. String distributedDeviceName = createDistributedDevice(localDevices, winningClusterId); s_logger.info("Created distributed device on local devices"); VPlexDistributedDeviceInfo distDeviceInfo = discoveryMgr .findDistributedDevice(distributedDeviceName, true); if (distDeviceInfo == null) { s_logger.error("Distributed device {} was successfully created but not returned by the VPLEX system", distributedDeviceName); throw VPlexApiException.exceptions.failedGettingDistributedDevice(distributedDeviceName); } distDeviceInfo.setLocalDeviceInfo(localDevices); clusterId = distDeviceInfo.getClusterId(); deviceName = distDeviceInfo.getName(); devicePath = distDeviceInfo.getPath(); } else { // Should only be a single local device. VPlexDeviceInfo deviceInfo = localDevices.get(0); clusterId = deviceInfo.getCluster(); deviceName = deviceInfo.getName(); devicePath = deviceInfo.getPath(); } // Create virtual volume createVirtualVolume(devicePath, thinEnabled); s_logger.info("Created virtual volume on device {}", devicePath); VPlexVirtualVolumeInfo virtualVolumeInfo = new VPlexVirtualVolumeInfo(); StringBuilder volumeNameBuilder = new StringBuilder(); volumeNameBuilder.append(deviceName); volumeNameBuilder.append(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX); if (findVirtualVolume) { // For bulk volume creation we shouldn't use findVirtualVolume as true, rather findVirtualVolumes should be called // separately after createVirtualVolumes. virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterId, volumeNameBuilder.toString(), true, true); } else { virtualVolumeInfo.setName(volumeNameBuilder.toString()); virtualVolumeInfo.addCluster(clusterId); } return virtualVolumeInfo; } catch (Exception e) { // An error occurred. Clean up any VPLEX artifacts created for // virtual volume and unclaim the storage volumes. s_logger.info("Exception occurred creating virtual volume, attempting to cleanup VPLEX artifacts"); try { // This will look for any artifacts, starting with a virtual // volume, that use the passed native volume info and destroy // them and then unclaim the volume. deleteVirtualVolume(nativeVolumeInfoList); } catch (Exception ex) { s_logger.error("Failed attempting to cleanup VPLEX after failed attempt " + "to create a new virtual volume", ex); } throw e; } } /** * Creates and attaches mirror device to the source device. * * @param virtualVolume The virtual volume information to which mirror will be attached * @param nativeVolumeInfoList The native volume information. * @param discoveryRequired true if the passed native volumes are newly * exported and need to be discovered by the VPlex. * @param preserveData true if the native volume data should be preserved * during mirror device creation. * * @return The VPlex device info that is attached as a mirror. * * @throws VPlexApiException When an error occurs creating and attaching device as a mirror */ VPlexDeviceInfo createDeviceAndAttachAsMirror(VPlexVirtualVolumeInfo virtualVolume, List<VolumeInfo> nativeVolumeInfoList, boolean discoveryRequired, boolean preserveData) throws VPlexApiException { if (nativeVolumeInfoList.size() != 1) { throw VPlexApiException.exceptions.oneDeviceRequiredForMirror(); } // Find the storage volumes corresponding to the passed native // volume information, discovery them if required. List<VPlexClusterInfo> clusterInfoList = new ArrayList<VPlexClusterInfo>(); Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap = findStorageVolumes( nativeVolumeInfoList, discoveryRequired, clusterInfoList, null); // Claim the storage volumes claimStorageVolumes(storageVolumeInfoMap, preserveData); s_logger.info("Claimed storage volumes"); // Try and build up the VPLEX local device from the claimed storage // volume and attach the local device to the local device of the // passed virtual volume to form the local mirror. If we get an error, // clean up the VPLEX artifacts and unclaim the storage volume. try { // Create extents List<VPlexStorageVolumeInfo> storageVolumeInfoList = new ArrayList<VPlexStorageVolumeInfo>(); for (VolumeInfo nativeVolumeInfo : nativeVolumeInfoList) { storageVolumeInfoList.add(storageVolumeInfoMap.get(nativeVolumeInfo)); } createExtents(storageVolumeInfoList); s_logger.info("Created extents on storage volumes"); // Find the extents just created and create local devices on // those extents. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); List<VPlexExtentInfo> extentInfoList = discoveryMgr.findExtents(storageVolumeInfoList); createLocalDevices(extentInfoList); s_logger.info("Created local devices on extents"); // Find the local devices just created.There will be only one local device List<VPlexDeviceInfo> localDevices = discoveryMgr.findLocalDevices(extentInfoList); VPlexVirtualVolumeInfo vplexVolumeInfo = findVirtualVolumeAndUpdateInfo(virtualVolume.getName(), discoveryMgr); String sourceDeviceName = vplexVolumeInfo.getSupportingDevice(); if (virtualVolume.getLocality().equals(VPlexApiConstants.LOCAL_VIRTUAL_VOLUME)) { // Find the source local device. VPlexDeviceInfo sourceLocalDevice = discoveryMgr.findLocalDevice(sourceDeviceName); if (sourceLocalDevice == null) { throw VPlexApiException.exceptions.cantFindLocalDevice(sourceDeviceName); } s_logger.info("Found the local device {}", sourceLocalDevice.getPath()); // Attach mirror device to the source volume device deviceAttachMirror(sourceLocalDevice.getPath(), localDevices.get(0).getPath(), null); s_logger.info("Added {} as a mirror to the source device {}", localDevices .get(0).getPath(), sourceLocalDevice.getPath()); } else { // Find the distributed device VPlexDistributedDeviceInfo distributedDeviceInfo = discoveryMgr.findDistributedDevice(sourceDeviceName); if (distributedDeviceInfo == null) { throw VPlexApiException.exceptions.cantFindDistDevice(sourceDeviceName); } String sourceDevicePath = null; List<VPlexDistributedDeviceComponentInfo> ddComponents = discoveryMgr .getDistributedDeviceComponents(distributedDeviceInfo); for (VPlexDistributedDeviceComponentInfo ddComponent : ddComponents) { discoveryMgr.updateDistributedDeviceComponent(ddComponent); if (ddComponent.getCluster().equals(localDevices.get(0).getCluster())) { sourceDevicePath = ddComponent.getPath(); break; } } if (sourceDevicePath == null) { throw VPlexApiException.exceptions.couldNotFindComponentForDistDevice( distributedDeviceInfo.getName(), localDevices.get(0) .getCluster()); } // Attach mirror device to one of the device in the distributed device where // mirror device and the source device are in the same cluster deviceAttachMirror(sourceDevicePath, localDevices.get(0).getPath(), null); s_logger.info("Added {} as a mirror to the device {}", localDevices .get(0).getPath(), sourceDevicePath); } // update the vplexVolumeInfo object so we can tell if thin-capability changed vplexVolumeInfo = findVirtualVolumeAndUpdateInfo(virtualVolume.getName(), discoveryMgr); virtualVolume.setThinCapable(vplexVolumeInfo.getThinCapable()); virtualVolume.setThinEnabled(vplexVolumeInfo.getThinEnabled()); // return mirror device return localDevices.get(0); } catch (Exception e) { s_logger.error("Exception occurred creating mirror device"); throw e; } } /** * Attaches mirror device to the source device * * @param locality The constant that tells if its local or distributed volume * @param sourceVirtualVolumeName Name of the virtual volume * @param mirrorDeviceName Name of the mirror device * * @throws VPlexApiException When an error occurs attaching mirror device to the * source volume device */ public void attachMirror(String locality, String sourceVirtualVolumeName, String mirrorDeviceName) throws VPlexApiException { VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexVirtualVolumeInfo vplexVirtualVolumeInfo = findVirtualVolumeAndUpdateInfo(sourceVirtualVolumeName, discoveryMgr); String sourceDeviceName = vplexVirtualVolumeInfo.getSupportingDevice(); // Find mirror device VPlexDeviceInfo mirrorLocalDevice = discoveryMgr.findLocalDevice(mirrorDeviceName); if (mirrorLocalDevice == null) { throw VPlexApiException.exceptions.cantFindLocalDevice(mirrorDeviceName); } String sourceDevicePath = null; if (locality.equals(VPlexApiConstants.LOCAL_VIRTUAL_VOLUME)) { // Find local source device VPlexDeviceInfo sourceLocalDevice = discoveryMgr.findLocalDevice(sourceDeviceName); if (sourceLocalDevice == null) { throw VPlexApiException.exceptions.cantFindLocalDevice(sourceDeviceName); } sourceDevicePath = sourceLocalDevice.getPath(); } else { // Find the distributed device VPlexDistributedDeviceInfo distributedDeviceInfo = discoveryMgr.findDistributedDevice(sourceDeviceName); if (distributedDeviceInfo == null) { throw VPlexApiException.exceptions.cantFindDistDevice(sourceDeviceName); } List<VPlexDistributedDeviceComponentInfo> ddComponents = discoveryMgr .getDistributedDeviceComponents(distributedDeviceInfo); for (VPlexDistributedDeviceComponentInfo ddComponent : ddComponents) { discoveryMgr.updateDistributedDeviceComponent(ddComponent); if (ddComponent.getCluster().equals(mirrorLocalDevice.getCluster())) { sourceDevicePath = ddComponent.getPath(); break; } } if (sourceDevicePath == null) { throw VPlexApiException.exceptions.couldNotFindComponentForDistDevice( distributedDeviceInfo.getName(), mirrorLocalDevice.getCluster()); } } // Attach mirror device to the source volume device deviceAttachMirror(sourceDevicePath, mirrorLocalDevice.getPath(), null); s_logger.info("Added ", mirrorLocalDevice.getPath() + " as a mirror to the source device " + sourceDevicePath); } /** * Deletes the virtual volume by destroying all the components (i.e, * extents, devices) that were created in the process of creating the * virtual and unclaiming the storage volume(s). * * @param nativeVolumeInfoList The same native volume info that was passed * when the virtual volume was created. * * @throws VPlexApiException When an error occurs deleted the virtual * volume. */ void deleteVirtualVolume(List<VolumeInfo> nativeVolumeInfoList) throws VPlexApiException { s_logger.info("Deleting virtual volume using native volume info"); // Get the name(s) of the volume(s) that were used to create // the virtual volume. List<String> nativeVolumeNames = new ArrayList<String>(); for (VolumeInfo nativeVolumeInfo : nativeVolumeInfoList) { nativeVolumeNames.add(nativeVolumeInfo.getVolumeName()); } // Build the virtual volume name from the names of the // passed volumes and delete it. deleteVirtualVolume(buildVirtualVolumeName(nativeVolumeNames), true, false); } /** * Deletes the virtual volume by destroying all the components (i.e, * extents, devices) that were created in the process of creating the * virtual and unclaiming the storage volume(s). * * @param virtualVolumeName The name of the virtual volume to be deleted. * @param unclaimVolumes true if the storage volumes should be unclaimed. * @param retryOnDismantleFailure When true, retries the delete if the * dismantle of the virtual volume fails, which can occur if the * volume was distributed and had previously been migrated. This is * due to a bug in the VPLEX management software (zeph-q24801) * * @throws VPlexApiException When an error occurs deleted the virtual * volume. */ void deleteVirtualVolume(String virtualVolumeName, boolean unclaimVolumes, boolean retryOnDismantleFailure) throws VPlexApiException { s_logger.info("Deleting virtual volume {}", virtualVolumeName); // Find the virtual volume. If it exists, we should be able to find it // on either cluster. VPlexVirtualVolumeInfo virtualVolumeInfo = null; VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); List<VPlexClusterInfo> clusterInfoList = discoveryMgr.getClusterInfoLite(); for (VPlexClusterInfo clusterInfo : clusterInfoList) { virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterInfo.getName(), virtualVolumeName, false); if (virtualVolumeInfo != null) { break; } } // Tear down the virtual volume if it is found. This will tear down // the entire virtual volume stack and unclaim the storage volumes. if (virtualVolumeInfo != null) { s_logger.info("Tearing down virtual volume {}", virtualVolumeName); try { dismantleResource(virtualVolumeInfo.getPath(), unclaimVolumes, false); } catch (VPlexApiException vae) { if (retryOnDismantleFailure) { s_logger.info("Tear down of virtual volume {} failed: {}", virtualVolumeName, vae.getMessage()); // If the issue is the bug referenced in the javadoc, the // virtual volume will actually be gone, so in the next // call the volume will not be found, and the else path // will be taken. Note that the distributed device will // also be deleted and so will not be found, so what ends // up happening is the local devices are dismantled. This // should work successfully and in the end, the virtual // volume will be successfully cleanup, just as if the // original called succeeded. deleteVirtualVolume(virtualVolumeName, unclaimVolumes, false); } else { throw vae; } } } else if (virtualVolumeName.endsWith(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX)) { // Otherwise, perhaps the virtual volume creation failed and // the delete is being called to cleanup any intermediate // resources in the virtual volume stack. // Build the distributed device name. String deviceName = virtualVolumeName.substring(0, virtualVolumeName.indexOf(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX)); if (deviceName.startsWith(VPlexApiConstants.DIST_DEVICE_PREFIX)) { s_logger.info("Tearing down distributed device for virtual volume {}", virtualVolumeName); deleteDistributedDevice(deviceName); } else { s_logger.info("Tearing down local device for virtual volume {}", virtualVolumeName); deleteLocalDevice(deviceName); } } } /** * Deletes the virtual volume and leaves the underlying structure intact. * * @param virtualVolumeName The name of the virtual volume to be destroyed. * * @throws VPlexApiException When an error occurs destroying the virtual * volume. */ void destroyVirtualVolume(String virtualVolumeName) throws VPlexApiException { s_logger.info("Destroying virtual volume {}", virtualVolumeName); // Find the virtual volume. If it exists, we should be able to find it // on either cluster. VPlexVirtualVolumeInfo virtualVolumeInfo = null; VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); List<VPlexClusterInfo> clusterInfoList = discoveryMgr.getClusterInfoLite(); for (VPlexClusterInfo clusterInfo : clusterInfoList) { virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterInfo.getName(), virtualVolumeName, false); if (virtualVolumeInfo != null) { break; } } if (virtualVolumeInfo != null) { ClientResponse response = null; try { URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DESTROY_VIRTUAL_VOLUME); s_logger.info("Destroy virtual volume URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_V, virtualVolumeInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Destroy POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Destroy virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Destroying of resource is completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.deleteVolumeFailureStatus( virtualVolumeName, String.valueOf(response.getStatus()), cause); } } s_logger.info("Resource {} successfully destroyed.", virtualVolumeInfo.getPath()); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDeleteVolume(virtualVolumeName, e); } finally { if (response != null) { response.close(); } } } } /** * Expands the virtual volume with the passed name to its full expandable * capacity. This API would be invoked after natively expanding the backend * volume(s) of the virtual volume to provide additional capacity or say * migrating the backend volume(s) to volume(s) with a larger capacity. * * @param virtualVolumeName The name of the virtual volume. * @param expansionStatusRetryCount Retry count to check virtual volume's expansion status. * @param expansionStatusSleepTime Sleep time in between expansion status check retries. * * @throws VPlexApiException When an exception occurs expanding the volume. */ VPlexVirtualVolumeInfo expandVirtualVolume(String virtualVolumeName, int expansionStatusRetryCount, long expansionStatusSleepTime) throws VPlexApiException { s_logger.info("Expanding virtual volume {}", virtualVolumeName); // Find the virtual volume. If it exists, we should be able to find it // on either cluster. String clusterName = null; VPlexVirtualVolumeInfo virtualVolumeInfo = null; VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); List<VPlexClusterInfo> clusterInfoList = discoveryMgr.getClusterInfoLite(); for (VPlexClusterInfo clusterInfo : clusterInfoList) { virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterInfo.getName(), virtualVolumeName, false); if (virtualVolumeInfo != null) { clusterName = clusterInfo.getName(); break; } } if (virtualVolumeInfo == null) { throw VPlexApiException.exceptions.cantFindRequestedVolume(virtualVolumeName); } s_logger.info("Found virtual volume"); ClientResponse response = null; try { URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_EXPAND_VIRTUAL_VOLUME); s_logger.info("Expand virtual volume URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_V, virtualVolumeInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Expand virtual volume POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Expand virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Virtual volume expansion completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.expandVolumeFailureStatus( virtualVolumeName, String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully expanded virtual volume"); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedExpandVolume(virtualVolumeName, e); } finally { if (response != null) { response.close(); } } // Update the virtual volume info with the new capacity. updateVirtualVolumeInfoAfterExpansion(clusterName, virtualVolumeInfo, expansionStatusRetryCount, expansionStatusSleepTime); s_logger.info("Updated virtual volume info"); return virtualVolumeInfo; } /** * Updates the virtual volume information after an expansion is executed for * the purpose of updating the block count so that the volume specifies the * newly expanded capacity. Waits for a specified period of time for the * expansion to complete, which should happen quickly. * * @param clusterName The cluster for the virtual volume. * @param virtualVolumeInfo A reference to the virtual volume info. * @param expansionStatusRetryCount Retry count to check virtual volume's expansion status. * @param expansionStatusSleepTime Sleep time in between expansion status check retries. * * @throws VPlexApiException When an exception occurs attempting to update * the virtual volume info. */ private void updateVirtualVolumeInfoAfterExpansion(String clusterName, VPlexVirtualVolumeInfo virtualVolumeInfo, int expansionStatusRetryCount, long expansionStatusSleepTime) throws VPlexApiException { int retryCount = 0; String expansionStatus = null; String expandableCapacity = null; boolean expansionCompleted = false; VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); while (++retryCount <= expansionStatusRetryCount) { try { // Pause before obtaining the volume info. VPlexApiUtils.pauseThread(expansionStatusSleepTime); discoveryMgr.updateVirtualVolumeInfo(clusterName, virtualVolumeInfo); // Get the expansion status and the expandable capacity. We need // to check both. We want the updated virtual volume info to specify // the new block count after the expansion has completed. In this // way we properly reflect the new provisioned capacity in ViPR. // However due to VPLEX issue (zeph-q40729), at the time the expansion // status becomes "null" indicating the expansion has completed, it // may be the case that the block count and expandable capacity // do not reflect the updated values resulting form the expansion. // Therefore, we check both that the expansion has completed and // also that the expandable capacity is 0. At this point the block // count should also be updated, and the proper capacity can be // set in ViPR. expansionStatus = virtualVolumeInfo.getExpansionStatus(); s_logger.info("Expansion status is {}", expansionStatus); expandableCapacity = virtualVolumeInfo.getExpandableCapacity(); s_logger.info("Expandable capacity is {}", expandableCapacity); // Now check if the expansion has completed by checking the expansion // status and expandable capacity. if (((expansionStatus == null) || (VPlexApiConstants.NULL_ATT_VAL.equals(expansionStatus))) && (VPlexApiConstants.NO_EXPANDABLE_CAPACITY.equals(expandableCapacity))) { // The expansion has completed. expansionCompleted = true; break; } else if (!VPlexVirtualVolumeInfo.ExpansionStatus.FAILED.equals(expansionStatus)) { // The expansion status is null indicating the expansion has completed but // the expandable capacity has yet to be updated, or the expansion status // is in-progress or unknown. In this case we just continue and retry. continue; } else { // The expansion status indicates the expansion has failed. break; } } catch (Exception e) { s_logger.error("An error occurred updating the virtual volume info: {}", e.getMessage()); if (retryCount < expansionStatusRetryCount) { s_logger.info("Trying again to get virtual volume info"); } else { throw VPlexApiException.exceptions.exceptionGettingVolumeExpansionStatus(virtualVolumeInfo.getName(), e); } } } // If the VPLEX volume expansion is not completed, throw an error indicating why. if (!expansionCompleted) { s_logger.info(String.format("After %s retries with wait of %s ms between each retry volume %s " + "expansion status has not completed", String.valueOf(expansionStatusRetryCount), String.valueOf(expansionStatusSleepTime), virtualVolumeInfo.getName())); if (VPlexVirtualVolumeInfo.ExpansionStatus.FAILED.equals(expansionStatus)) { throw VPlexApiException.exceptions.vplexVolumeExpansionFailed(virtualVolumeInfo.getName()); } else if (VPlexVirtualVolumeInfo.ExpansionStatus.INPROGRESS.equals(expansionStatus)) { throw VPlexApiException.exceptions.vplexVolumeExpansionIsStillInProgress(virtualVolumeInfo.getName()); } else if (VPlexVirtualVolumeInfo.ExpansionStatus.UNKNOWN.equals(expansionStatus)) { throw VPlexApiException.exceptions.vplexVolumeExpansionIsInUnknownState(virtualVolumeInfo.getName()); } else { throw VPlexApiException.exceptions.vplexVolumeExpansionBlockCountNotUpdated(virtualVolumeInfo.getName()); } } } /** * Find the storage volumes identified by the passed native volume info, * discovering the storage volumes if required. * * @param nativeVolumeInfoList The native volume information. * @param discoveryRequired true if the passed native volumes are newly * exported and need to be discovered by the VPlex. * @param clusterInfoList [OUT] param set to the cluster information. * @param clusterName if non-null, search will be restricted to the named cluster * * @throws VPlexApiException When an error occurs finding the storage * volumes or the storage volumes are not all found. */ Map<VolumeInfo, VPlexStorageVolumeInfo> findStorageVolumes(List<VolumeInfo> nativeVolumeInfoList, boolean discoveryRequired, List<VPlexClusterInfo> clusterInfoList, String clusterName) throws VPlexApiException { // If the volume(s) passed are newly exported to the VPlex, they may // need to be discovered before they can be used. If the discovery // required flag is true, we execute a discovery step so that the // volumes are available to the VPlex. Note that we will try for a while // to give the newly exported volumes some time to be accessible by // the VPlex. Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap = null; VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); if (discoveryRequired) { s_logger.info("Storage volume discovery is required."); int retryCount = 0; while (++retryCount <= VPlexApiConstants.FIND_STORAGE_VOLUME_RETRY_COUNT) { try { // Execute the re-discover command. s_logger.info("Executing storage volume discovery try {} of {}", retryCount, VPlexApiConstants.FIND_STORAGE_VOLUME_RETRY_COUNT); List<String> storageSystemGuids = new ArrayList<String>(); for (VolumeInfo nativeVolumeInfo : nativeVolumeInfoList) { String storageSystemGuid = nativeVolumeInfo.getStorageSystemNativeGuid(); if (!storageSystemGuids.contains(storageSystemGuid)) { s_logger.info("Discover storage volumes on array {}", storageSystemGuid); storageSystemGuids.add(storageSystemGuid); } } discoveryMgr.rediscoverStorageSystems(storageSystemGuids); s_logger.info("Discovery completed"); // Get the cluster information. clusterInfoList.addAll(discoveryMgr.getClusterInfo(false, true, clusterName)); s_logger.info("Retrieved storage volume info for VPlex clusters"); // Find the back-end storage volumes. If a volume cannot be // found, an exception is thrown. storageVolumeInfoMap = discoveryMgr.findStorageVolumes(nativeVolumeInfoList, clusterInfoList); s_logger.info("Found storage volumes to use for virtual volume"); // Exit, no exceptions means all volumes found. break; } catch (VPlexApiException vae) { // If we reached the maximum retry count, then rethrow // the exception. if (retryCount == VPlexApiConstants.FIND_STORAGE_VOLUME_RETRY_COUNT) { throw vae; } // Otherwise, if an exception occurs, it is likely because a // storage volume was not found. Wait for a bit and execute // the the discovery again. clusterInfoList.clear(); VPlexApiUtils.pauseThread(VPlexApiConstants.FIND_STORAGE_VOLUME_SLEEP_TIME_MS); } } } else { s_logger.info("Storage volume discovery is not required."); // Get the cluster information. if (clusterInfoList.isEmpty()) { clusterInfoList.addAll(discoveryMgr.getClusterInfo(false, true, clusterName)); s_logger.info("Retrieved storage volume info for VPlex clusters"); } // Find the backend storage volumes. If a volume cannot be // found, then an exception will be thrown. storageVolumeInfoMap = discoveryMgr.findStorageVolumes(nativeVolumeInfoList, clusterInfoList); s_logger.info("Found storage volumes"); } return storageVolumeInfoMap; } /** * Claims the VPlex volumes in the passed map. * * @param storageVolumeInfoMap The VPlex volumes to claim. * @param preserveData true if the native volume data should be preserved * during virtual volume creation. * * @throws VPlexApiException When an error occurs claiming the volumes. */ void claimStorageVolumes( Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap, boolean preserveData) throws VPlexApiException { URI requestURI = _vplexApiClient.getBaseURI().resolve(VPlexApiConstants.URI_CLAIM_VOLUME); s_logger.info("Claim storage volumes URI is {}", requestURI.toString()); Iterator<Entry<VolumeInfo, VPlexStorageVolumeInfo>> volumeIter = storageVolumeInfoMap.entrySet().iterator(); List<String> storageVolumeContextPaths = new ArrayList<String>(); while (volumeIter.hasNext()) { ClientResponse response = null; Entry<VolumeInfo, VPlexStorageVolumeInfo> entry = volumeIter.next(); VolumeInfo volumeInfo = entry.getKey(); String volumeName = volumeInfo.getVolumeName(); s_logger.info("Claiming volume {}", volumeInfo.getVolumeWWN()); try { VPlexStorageVolumeInfo storageVolumeInfo = entry.getValue(); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, storageVolumeInfo.getPath()); argsMap.put(VPlexApiConstants.ARG_DASH_N, volumeName); if (preserveData) { argsMap.put(VPlexApiConstants.ARG_APPC, ""); } if (volumeInfo.getIsThinProvisioned()) { argsMap.put(VPlexApiConstants.ARG_THIN_REBUILD, ""); } JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Claim storage volumes POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Claim storage volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Claiming storage volume is completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.claimVolumeFailureStatus( volumeInfo.getVolumeWWN(), String.valueOf(response.getStatus()), cause); } } // If successfully claimed, update the VPlex storage volume // info with the name assigned when the volume was claimed. storageVolumeInfo.setName(volumeName); // Also, update the context path. String currentPath = storageVolumeInfo.getPath(); int endIndex = currentPath.lastIndexOf("/"); String newPath = currentPath.substring(0, endIndex + 1) + volumeName; storageVolumeInfo.setPath(newPath); s_logger.info("Successfully claimed storage volume {}", volumeInfo.getVolumeWWN()); // Update storage volumes contexts to be refreshed. String contextPath = currentPath.substring(0, endIndex); if (!storageVolumeContextPaths.contains(contextPath)) { storageVolumeContextPaths.add(contextPath); } } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedClaimVolume( volumeInfo.getVolumeWWN(), e); } finally { if (response != null) { response.close(); } } } // Workaround for VPLEX issue zeph-q34217 as seen in Jira 9121 _vplexApiClient.getDiscoveryManager().refreshContexts(storageVolumeContextPaths); } /** * Creates extents for the passed VPlex storage volumes. The extents so * created consume the entire volume. * * @param storageVolumeInfoList The VPlex storage volumes on which to create * extents. * * @throws VPlexApiException When an error occurs creating the extents, */ void createExtents(List<VPlexStorageVolumeInfo> storageVolumeInfoList) throws VPlexApiException { ClientResponse response = null; try { URI requestURI = _vplexApiClient.getBaseURI().resolve(VPlexApiConstants.URI_CREATE_EXTENT); s_logger.info("Create extent URI is {}", requestURI.toString()); StringBuilder volumePathsBuilder = new StringBuilder(); Iterator<VPlexStorageVolumeInfo> volumeInfoIter = storageVolumeInfoList.iterator(); while (volumeInfoIter.hasNext()) { volumePathsBuilder.append(volumeInfoIter.next().getPath()); if (volumeInfoIter.hasNext()) { volumePathsBuilder.append(","); } } s_logger.info("Creating extents on storage volumes {}", volumePathsBuilder.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, volumePathsBuilder.toString()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Create extent POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Create extent response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Extent creation completing asyncronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.createExtentFailureStatus( String.valueOf(response.getStatus()), cause); } } s_logger.info("Extent creation successful"); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedCreateExtent(e); } finally { if (response != null) { response.close(); } } } /** * Create local devices on the passed extents. The local devices so created * consume the entire extent. * * @param extentInfoList The list of extents on which to create local * devices. * * @throws VPlexApiException When an error occurs creating the local * devices. */ void createLocalDevices(List<VPlexExtentInfo> extentInfoList) throws VPlexApiException { URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_CREATE_LOCAL_DEVICE); s_logger.info("Create local device URI is {}", requestURI.toString()); Iterator<VPlexExtentInfo> extentInfoIter = extentInfoList.iterator(); while (extentInfoIter.hasNext()) { ClientResponse response = null; try { VPlexExtentInfo extentInfo = extentInfoIter.next(); s_logger.info("Create local device for extent {}", extentInfo.getName()); VPlexStorageVolumeInfo storageVolumeInfo = extentInfo.getStorageVolumeInfo(); String storageVolumeName = storageVolumeInfo.getName(); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_E, extentInfo.getPath()); argsMap.put(VPlexApiConstants.ARG_DASH_N, VPlexApiConstants.DEVICE_PREFIX + storageVolumeName); argsMap.put(VPlexApiConstants.ARG_DASH_G, VPlexApiConstants.ARG_GEOMETRY_RAID0); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Create local device POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Create local device response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Local device creation completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.createLocalDeviceFailureStatus( String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully created local device"); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedCreateLocalDevice(e); } finally { if (response != null) { response.close(); } } } } /** * Creates a distributed device from the passed local devices. * * @param localDeviceInfoList The local devices to be used to construct the * distributed device. * @param winningClusterId Used to set detach rules for distributed volumes. * * @return The name of the distributed device. * * @throws VPlexApiException When an error occurs creating the distributed * device. */ private String createDistributedDevice(List<VPlexDeviceInfo> localDeviceInfoList, String winningClusterId) throws VPlexApiException { ClientResponse response = null; try { URI requestURI = _vplexApiClient.getBaseURI().resolve(VPlexApiConstants.URI_CREATE_DIST_DEVICE); s_logger.info("Create distributed device URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); StringBuilder pathBuilder = new StringBuilder(); StringBuilder nameBuilder = new StringBuilder(VPlexApiConstants.DIST_DEVICE_PREFIX); for (VPlexDeviceInfo localDeviceInfo : localDeviceInfoList) { if (pathBuilder.length() != 0) { pathBuilder.append(","); } pathBuilder.append(localDeviceInfo.getPath()); nameBuilder.append(VPlexApiConstants.DIST_DEVICE_NAME_DELIM); nameBuilder.append(localDeviceInfo.getName().substring( VPlexApiConstants.DEVICE_PREFIX.length())); } String rulesetName = VPlexApiConstants.CLUSTER_1_DETACHES; if (winningClusterId.equals(VPlexApiConstants.CLUSTER_2_ID)) { rulesetName = VPlexApiConstants.CLUSTER_2_DETACHES; } s_logger.info("Creating distributed device from local devices {}", pathBuilder.toString()); argsMap.put(VPlexApiConstants.ARG_DASH_N, nameBuilder.toString()); argsMap.put(VPlexApiConstants.ARG_DASH_D, pathBuilder.toString()); argsMap.put(VPlexApiConstants.ARG_DASH_R, rulesetName); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Create distributed device POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Create distributed device response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Distributed device creation completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.createDistDeviceFailureStatus( String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully created distributed device {}", nameBuilder.toString()); return nameBuilder.toString(); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedCreateDistDevice(e); } finally { if (response != null) { response.close(); } } } /** * Creates a virtual volume for the device with the passed context path. * * @param devicePath The context path of a local or distributed device. * @param thinEnabled If true, request VPLEX to create the volume as thin-enabled. * * @throws VPlexApiException When an error occurs creating the virtual volume. */ private void createVirtualVolume(String devicePath, Boolean thinEnabled) throws VPlexApiException { ClientResponse response = null; try { s_logger.info("Create virtual volume for device {}", devicePath); URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_CREATE_VIRTUAL_VOLUME); s_logger.info("Create virtual volume URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_R, devicePath); if (thinEnabled) { argsMap.put(VPlexApiConstants.ARG_THIN_ENABLED, ""); } JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Create virtual volume POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Create virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Virtual volume creation completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.createVolumeFailureStatus( String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully created virtual volume for device {}", devicePath); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedCreateVolume(e); } finally { if (response != null) { response.close(); } } } /** * Creates the name given a virtual volume when it is created. * * @param nativeVolumeNames The names of the volume(s) passed when the * virtual volume is created. * * @return The name of the virtual volume. */ private String buildVirtualVolumeName(List<String> nativeVolumeNames) { s_logger.info("Building virtual volume name from native volume info"); StringBuilder nameBuilder = new StringBuilder(); if (nativeVolumeNames.size() == 1) { // Simple virtual volume. nameBuilder.append(VPlexApiConstants.DEVICE_PREFIX); nameBuilder.append(nativeVolumeNames.get(0)); nameBuilder.append(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX); } else { // Distributed virtual volume. nameBuilder.append(VPlexApiConstants.DIST_DEVICE_PREFIX); for (String nativeVolumeName : nativeVolumeNames) { nameBuilder.append(VPlexApiConstants.DIST_DEVICE_NAME_DELIM); nameBuilder.append(nativeVolumeName); } nameBuilder.append(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX); } s_logger.info("Virtual volume name is {}", nameBuilder.toString()); return nameBuilder.toString(); } /** * Tears down a virtual volume, distributed device, or local device. * * @param resourcePath The context path of the virtual volume or device. * @param unclaimVolumes true if the storage volumes should be unclaimed. * @param isDevice True if the resource is a device, false if it's a virtual volume. * * @throws VPlexApiException When an error occurs tearing down the resource. */ private void dismantleResource(String resourcePath, boolean unclaimVolumes, boolean isDevice) throws VPlexApiException { ClientResponse response = null; try { URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DISMANTLE); s_logger.info("Dismantle resource URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); if (isDevice) { argsMap.put(VPlexApiConstants.ARG_DASH_R, resourcePath); } else { argsMap.put(VPlexApiConstants.ARG_DASH_V, resourcePath); } if (unclaimVolumes) { argsMap.put(VPlexApiConstants.ARG_UNCLAIM, ""); } JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Dismantle POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Delete virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Dismantling of resource is completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.dismantleResourceFailureStatus( resourcePath, String.valueOf(response.getStatus()), cause); } } // It has been observed that sometimes the VPlex reports SUCCESS but doesn't actually // do the dismantle, reporting a response containing "will not be dismantled because". // To avoid subsequent silent/unexplained failures, we test for that message and // if found throw an Exception. if (responseStr.contains(VPlexApiConstants.DISMANTLE_ERROR_MSG)) { s_logger.info("SUCCESS Response string contains DISMANTLE_ERROR_MSG string: " + responseStr); throw VPlexApiException.exceptions.dismantleResourceFailureStatus( resourcePath, String.valueOf(response.getStatus()), responseStr); } s_logger.info("Resource {} successfully dismantled.", resourcePath); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDismantleResource(resourcePath, e); } finally { if (response != null) { response.close(); } } } /** * Attempts to find and delete the passed distributed device. Will attempt * to dismantle the distributed device by destroying all components used to * build up the device (i.e, extents and local volumes). * * @param deviceName The name of the distributed device. * * @throws VPlexApiException When an error occurs deleting the distributed * device. */ private void deleteDistributedDevice(String deviceName) throws VPlexApiException { s_logger.info("Deleting distributed device {}", deviceName); // Find the distributed device. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexDistributedDeviceInfo distDeviceInfo = discoveryMgr .findDistributedDevice(deviceName); if (distDeviceInfo != null) { // If the distributed device is found, dismantle it. s_logger.info("Tearing down distributed device {}", deviceName); dismantleResource(distDeviceInfo.getPath(), true, true); } else { // Otherwise, delete any locals devices that might exist // for the distributed device with the passed name. s_logger.info("Tearing down local devices for distributed device {}", deviceName); StringTokenizer tokenizer = new StringTokenizer( deviceName.substring(VPlexApiConstants.DIST_DEVICE_PREFIX.length()), VPlexApiConstants.DIST_DEVICE_NAME_DELIM); while (tokenizer.hasMoreTokens()) { deleteLocalDevice(VPlexApiConstants.DEVICE_PREFIX + tokenizer.nextToken()); } } } /** * Attempts to find and delete the local device with the passed name.Will * attempt to dismantle the local device by destroying all components used * to build up the device (i.e, extents). * * @param deviceInfo the backend storage volume info for the device. * * @throws VPlexApiException When an error occurs deleting the local * device. */ void deleteLocalDevice(VolumeInfo deviceInfo) throws VPlexApiException { String deviceName = VPlexApiConstants.DEVICE_PREFIX + deviceInfo.getVolumeName(); deleteLocalDevice(deviceName); } /** * Attempts to find and delete the local device with the passed name. Will * attempt to dismantle the local device by destroying all components used * to build up the device (i.e, extents). * * @param deviceName The name of the local device. * * @throws VPlexApiException When an error occurs deleting the distributed * device. */ void deleteLocalDevice(String deviceName) throws VPlexApiException { s_logger.info("Deleting local device {}", deviceName); // Find the local device. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexDeviceInfo deviceInfo = discoveryMgr.findLocalDevice(deviceName); if (deviceInfo != null) { // If the local device is found, dismantle it. s_logger.info("Tearing down local device {}", deviceName); dismantleResource(deviceInfo.getPath(), true, true); } else if (deviceName.startsWith(VPlexApiConstants.DEVICE_PREFIX)) { // Otherwise delete the extent that may exist for // this local device. s_logger.info("Destroying extents for local device {}", deviceName); StringBuilder nameBuilder = new StringBuilder(VPlexApiConstants.EXTENT_PREFIX); nameBuilder.append(deviceName.substring(VPlexApiConstants.DEVICE_PREFIX.length())); nameBuilder.append(VPlexApiConstants.EXTENT_SUFFIX); deleteExtent(nameBuilder.toString()); } } /** * Attempts to find and delete the extent with the passed name. If * successfully delete, the method will also unclaim the storage volume from * which the extent was built. * * @param extentName The name of the extent. * * @throws When an error occurs deleting the extent or subsequently * unclaiming the storage volume. */ void deleteExtent(String extentName) throws VPlexApiException { s_logger.info("Deleting extent {}", extentName); // Find the extent. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexExtentInfo extentInfo = discoveryMgr.findExtent(extentName); // Get the storage volume name. String storageVolumeName = extentName.substring( VPlexApiConstants.EXTENT_PREFIX.length(), extentName.indexOf(VPlexApiConstants.EXTENT_SUFFIX)); // If the extent is found, destroy it. if (extentInfo != null) { ClientResponse response = null; try { s_logger.info("Destroying extent {}", extentName); URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DESTROY_EXTENT); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_S, extentInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Destroy extent POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Destroy extent response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Destroy extent completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.deleteExtentFailureStatus( extentName, String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully destroyed extent {}", extentName); // Now unclaim the volume. unclaimStorageVolume(storageVolumeName); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDeleteExtent(extentName, e); } finally { if (response != null) { response.close(); } } } else { // Otherwise, unclaim the storage volume that may have been // claimed for the extent. unclaimStorageVolume(storageVolumeName); } } /** * Unclaim the storage volume with the passed name. * * @param storageVolumeName The storage volume name. * * @throws VPlexApiException When an error occurs unclaiming the storage * volume. */ private void unclaimStorageVolume(String storageVolumeName) throws VPlexApiException { ClientResponse response = null; try { s_logger.info("Unclaim storage volume {}", storageVolumeName); VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexStorageVolumeInfo storageVolumeInfo = discoveryMgr .findStorageVolume(storageVolumeName); if (storageVolumeInfo != null) { s_logger.info("Found storage volume {} to be unclaimed", storageVolumeName); URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_UNCLAIM_VOLUME); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, storageVolumeInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Unclaim storage volume POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Unclaim storage volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Unclaim storage volume completing asynchrounously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.unclaimVolumeFailureStatus( storageVolumeName, String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully unclaimed storage volume {}", storageVolumeName); } } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedUnclaimVolume(storageVolumeName, e); } finally { if (response != null) { response.close(); } } } /** * Creates a distributed virtual volume from a non-distributed one plus a new unclaimed * remote storage volume. * * @param virtualVolume - Existing non-distributed virtual volume. * @param newRemoteVolume - Unclaimed storage volume in remote cluster. * @param discoveryRequired - Set if discovery required. * @param clusterId - Used to set the detach rule for the distributed volume. * @return - VPlexVirtualVolumeInfo representing the distributed virtual volume. * @throws VPlexApiException */ VPlexVirtualVolumeInfo createDistributedVirtualVolume(VPlexVirtualVolumeInfo virtualVolume, VolumeInfo newRemoteVolume, boolean discoveryRequired, String clusterId, String transferSize) throws VPlexApiException { // Determine the "local" device String virtualVolumeName = virtualVolume.getName(); String localDeviceName = virtualVolume.getSupportingDevice(); // Find the storage volumes corresponding to the passed remote // volume information, discovery them if required. s_logger.info("Find remote storage volume"); List<VolumeInfo> remoteVolumeInfoList = new ArrayList<VolumeInfo>(); remoteVolumeInfoList.add(newRemoteVolume); List<VPlexClusterInfo> clusterInfoList = new ArrayList<VPlexClusterInfo>(); Map<VolumeInfo, VPlexStorageVolumeInfo> storageVolumeInfoMap = findStorageVolumes( remoteVolumeInfoList, discoveryRequired, clusterInfoList, null); if (storageVolumeInfoMap.isEmpty()) { throw VPlexApiException.exceptions.cantDiscoverStorageVolume(newRemoteVolume.getVolumeWWN()); } // Claim the storage volumes claimStorageVolumes(storageVolumeInfoMap, false); s_logger.info("Claimed remote storage volume"); // Try and build up the VPLEX local device from the claimed storage // volume and attach the local device to the local device of the // passed virtual volume to form the local mirror. If we get an error, // clean up the VPLEX artifacts and unclaim the storage volume. VPlexDeviceInfo remoteDevice, localDevice; VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); try { // Create extents List<VPlexStorageVolumeInfo> storageVolumeInfoList = new ArrayList<VPlexStorageVolumeInfo>(); for (VolumeInfo nativeVolumeInfo : remoteVolumeInfoList) { storageVolumeInfoList.add(storageVolumeInfoMap.get(nativeVolumeInfo)); } createExtents(storageVolumeInfoList); s_logger.info("Created extent on remote storage volume"); // Find the extents just created and create local devices on // those extents. List<VPlexExtentInfo> extentInfoList = discoveryMgr.findExtents(storageVolumeInfoList); if (extentInfoList.isEmpty()) { throw VPlexApiException.exceptions .cantFindExtentForClaimedVolume(storageVolumeInfoList.get(0).getName()); } createLocalDevices(extentInfoList); s_logger.info("Created local devices on extents"); // Find the local device for the extent just created. List<VPlexDeviceInfo> remoteDevices = discoveryMgr.findLocalDevices(extentInfoList); if (remoteDevices.isEmpty()) { throw VPlexApiException.exceptions.cantFindLocalDeviceForExtent(extentInfoList.get(0).getName()); } remoteDevice = remoteDevices.get(0); s_logger.info("Found the remote device {}", remoteDevice.getPath()); // Find the local device. localDevice = discoveryMgr.findLocalDevice(localDeviceName); if (localDevice == null) { throw VPlexApiException.exceptions.cantFindLocalDevice(localDeviceName); } s_logger.info("Found the local device {}", localDevice.getPath()); // Now use "device attach-mirror" to create a distributed device from an already exported volume. String rulesetName = VPlexApiConstants.CLUSTER_1_DETACHES; if (clusterId.equals(VPlexApiConstants.CLUSTER_2_ID)) { rulesetName = VPlexApiConstants.CLUSTER_2_DETACHES; } deviceAttachMirror(localDevice.getPath(), remoteDevice.getPath(), rulesetName); s_logger.info("Finished device attach-mirror on device {}", localDevice.getPath()); } catch (Exception e) { // An error occurred. Clean up the VPLEX artifacts created for // the mirror and unclaim the storage volume. s_logger.info("Exception occurred creating and attaching remote mirror " + " to local VPLEX volume, attempting to cleanup VPLEX artifacts"); throw e; } try { // Find virtual volume. VPlexVirtualVolumeInfo vvInfo = discoveryMgr.findVirtualVolume( localDevice.getCluster(), virtualVolumeName, false); // If transferSize is set, set the rebuild size for the distributed device created by // device-attach mirror. VPlexDistributedDeviceInfo distDeviceInfo = discoveryMgr .findDistributedDevice(localDevice.getName()); if (transferSize != null) { String deviceName = distDeviceInfo.getName(); s_logger.info("Rebuild transfer size of {} will be set for device {}", transferSize, deviceName); setRebuildTransferSize(deviceName, transferSize); } // If the volume is using the default naming convention then when want to update the // name of the volume and the distributed device to reflect both backend volumes // used by the newly formed distributed volume. However, if the volume does not conform // to the default naming convention, such as an ingested volume or a volume that has // it name set via the custom naming configurations, then we want to leave the volume name // alone and simply update the device name, again assuming the previous device named // conforms to the standard naming convention. if (localDeviceName.length() > VPlexApiConstants.DEVICE_PREFIX.length()) { List<String> claimedVolumeNames = Arrays.asList(localDeviceName.substring(VPlexApiConstants.DEVICE_PREFIX.length())); if (VPlexApiUtils.volumeHasDefaultNamingConvention(virtualVolumeName, localDeviceName, false, claimedVolumeNames)) { // Update both the volume and supporting device names. String remoteName = remoteDevice.getName().replaceAll(VPlexApiConstants.DEVICE_PREFIX, ""); String newVvName = vvInfo.getName(); newVvName = newVvName.replaceFirst(VPlexApiConstants.DEVICE_PREFIX, VPlexApiConstants.DIST_DEVICE_PREFIX + VPlexApiConstants.DIST_DEVICE_NAME_DELIM); newVvName = newVvName.replaceFirst(VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX, ""); newVvName = newVvName + VPlexApiConstants.DIST_DEVICE_NAME_DELIM + remoteName + VPlexApiConstants.VIRTUAL_VOLUME_SUFFIX; vvInfo = renameVPlexResource(vvInfo, newVvName); String newDdName = distDeviceInfo.getName(); newDdName = newDdName.replaceFirst(VPlexApiConstants.DEVICE_PREFIX, VPlexApiConstants.DIST_DEVICE_PREFIX + VPlexApiConstants.DIST_DEVICE_NAME_DELIM); newDdName = newDdName + VPlexApiConstants.DIST_DEVICE_NAME_DELIM + remoteName; distDeviceInfo = renameVPlexResource(distDeviceInfo, newDdName); } else if (VPlexApiUtils.localDeviceHasDefaultNamingConvention(localDeviceName, claimedVolumeNames)) { // The volume name does not conform, but then supporting device does, so // update just the device name. String newDdName = distDeviceInfo.getName(); newDdName = newDdName.replaceFirst(VPlexApiConstants.DEVICE_PREFIX, VPlexApiConstants.DIST_DEVICE_PREFIX + VPlexApiConstants.DIST_DEVICE_NAME_DELIM); String remoteName = remoteDevice.getName().replaceAll(VPlexApiConstants.DEVICE_PREFIX, ""); newDdName = newDdName + VPlexApiConstants.DIST_DEVICE_NAME_DELIM + remoteName; distDeviceInfo = renameVPlexResource(distDeviceInfo, newDdName); } } return vvInfo; } catch (Exception e) { // An error occurred. Detach the mirror and clean up the VPLEX artifacts // created for the remote mirror and unclaim the storage volume. try { // Detach the mirror as the attach was successful. detachMirrorFromLocalVirtualVolume(virtualVolumeName, remoteDevice.getName(), true); // This will look for any artifacts, starting with a virtual // volume, that use the passed native volume info and destroy // them and then unclaim the volume. deleteVirtualVolume(Collections.singletonList(newRemoteVolume)); } catch (Exception ex) { s_logger.error("Failed attempting to cleanup VPLEX after failed attempt " + "to find and rename local virtual volume {} after remote mirror attached.", virtualVolume.getPath(), ex); } throw e; } } /** * Execute the "device attach-mirror" command. * * @param sourceDevicePath -- Path for the device in the existing virtual volume * @param mirrorDevicePath -- Path for the new device that will be attached as a mirror.It can be * in the local cluster of the source device or in the remote cluster. * @param rulesetName -- The rule set name apply when attaching a remote mirror to form a * distributed volume or null when attaching local mirrors. * @throws VPlexApiException */ private void deviceAttachMirror(String sourceDevicePath, String mirrorDevicePath, String rulesetName) throws VPlexApiException { ClientResponse response = null; try { s_logger.info("Device Attach Mirror for devices {} {}", sourceDevicePath, mirrorDevicePath); URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DEVICE_ATTACH_MIRROR); s_logger.info("Device Attach Mirror URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, sourceDevicePath); argsMap.put(VPlexApiConstants.ARG_DASH_M, mirrorDevicePath); if (rulesetName != null) { argsMap.put(VPlexApiConstants.ARG_DASH_R, rulesetName); } JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Device Attach Mirror POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Device Attach Mirror response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Virtual volume creation completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.attachMirrorFailureStatus( String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully attached mirror for device {}", sourceDevicePath); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedAttachMirror(e); } finally { if (response != null) { response.close(); } } } /** * Execute the "rebuild set-transfer-size" command. * * @param deviceName -- Distributed device on which we need to set the rebuild size * @param transferSize -- The transfer size that needs to be set in VPLEX * @throws VPlexApiException */ public void setRebuildTransferSize(String deviceName, String transferSize) { ClientResponse response = null; try { s_logger.info("Setting transfer size"); URI requestURI = _vplexApiClient.getBaseURI().resolve(VPlexApiConstants.URI_REBUILD_SET_TRANSFER_SIZE); s_logger.info("Rebuild Transfer size URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); StringBuilder deviceAndSize = new StringBuilder(); deviceAndSize.append(deviceName); deviceAndSize.append(" "); deviceAndSize.append(transferSize); argsMap.put(VPlexApiConstants.ARG_DEVICES, deviceAndSize.toString()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Rebuild Set Transfer Size POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Rebuild Set Transfer Size response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Rebuild Set Transfer Size command completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.setRebuildSetTransferSpeedFailureStatus( String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully executed rebuild set-transfer-size command"); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedSetTransferSize(e); } finally { if (response != null) { response.close(); } } } /** * Waits for the rebuild of the passed distributed volume to complete. * Will wait for up to 4 hours before it stops waiting and returns a * timeout result. * * @param virtualVolumeName The name of the virtual volume. * * @return A WaitOnRebuildResult indicating if the rebuild completed successfully, * failed, or we timed out waiting for it to complete. * * @throws VPlexApiException */ public WaitOnRebuildResult waitOnRebuildCompletion(String virtualVolumeName) throws VPlexApiException { VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); for (int retryCount = 0; retryCount < VPlexApiConstants.REBUILD_WAIT_RETRY_COUNT; retryCount++) { try { VPlexVirtualVolumeInfo virtualVolume = discoveryMgr.findVirtualVolume(virtualVolumeName, true); if (!VPlexVirtualVolumeInfo.Locality.distributed.name().equals(virtualVolume.getLocality())) { s_logger.error("Not a distributed device: {}", virtualVolumeName); return WaitOnRebuildResult.INVALID_REQUEST; } Map<String, Object> ddProperties = getDistributedDeviceProperties(virtualVolume.getSupportingDevice()); String rebuildStatus = ddProperties.get(VPlexApiConstants.REBUILD_STATUS_ATT_KEY).toString(); if (VPlexApiConstants.REBUILD_STATUS_DONE.equalsIgnoreCase(rebuildStatus)) { s_logger.info("Rebuild complete for volume: {}", virtualVolumeName); return WaitOnRebuildResult.SUCCESS; } if (rebuildStatus.equalsIgnoreCase(VPlexApiConstants.REBUILD_STATUS_ERROR)) { s_logger.info("Rebuild failed for volume: {}", virtualVolumeName); return WaitOnRebuildResult.FAILED; } s_logger.info("Waiting on rebuild to complete for volume: {}", virtualVolumeName); VPlexApiUtils.pauseThread(VPlexApiConstants.REBUILD_WAIT_SLEEP_TIME_MS); } catch (Exception e) { s_logger.warn("An exception occured checking rebuild status: {}. Retrying", e.getMessage(), e); VPlexApiUtils.pauseThread(VPlexApiConstants.REBUILD_WAIT_SLEEP_TIME_MS); } } s_logger.error("Rebuild timed out for volume: {}", virtualVolumeName); return WaitOnRebuildResult.TIMED_OUT; } /** * Returns the properties for a Distributed Device as name/value pairs. * * @param deviceName String * @return Map of property name to value * @throws VPlexApiException */ Map<String, Object> getDistributedDeviceProperties(String deviceName) throws VPlexApiException { VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexDistributedDeviceInfo ddInfo = discoveryMgr.findDistributedDevice(deviceName); URI requestURI = _vplexApiClient.getBaseURI().resolve( URI.create(VPlexApiConstants.VPLEX_PATH + ddInfo.getPath())); s_logger.info("getDistributedDeviceProperties URI is {}", requestURI.toString()); ClientResponse response = _vplexApiClient.get(requestURI); String responseStr = response.getEntity(String.class); response.close(); s_logger.info("getDistributedDeviceProperties name response is {}", responseStr); Map<String, Object> resultMap = VPlexApiUtils.getAttributesFromResponse(responseStr); return resultMap; } /** * Get the device components of a Distributed Device * * @param deviceName -- Distributed Device Name * @return -- List<VPlexResourceInfo> * @throws VPlexApiException */ List<VPlexDistributedDeviceComponentInfo> getDistributedDeviceComponents( String deviceName) throws VPlexApiException { VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexDistributedDeviceInfo ddInfo = discoveryMgr.findDistributedDevice(deviceName); return discoveryMgr.getDistributedDeviceComponents(ddInfo); } /** * Get the device components of a Distributed Device. * * @param ddInfo - The VPlex distributed device info for the device. * * @return A list of VPlexResourceInfo representing the components of the * distributed device. * * @throws VPlexApiException When an error occurs finding the components for * the distributed device. */ List<VPlexResourceInfo> getDistributedDeviceComponents( VPlexDistributedDeviceInfo ddInfo) throws VPlexApiException { URI requestURI = _vplexApiClient.getBaseURI().resolve( URI.create(VPlexApiConstants.VPLEX_PATH + ddInfo.getPath() + VPlexApiConstants.URI_DISTRIBUTED_DEVICE_COMP)); s_logger.info("getDistributedDeviceComponents URI is {}", requestURI.toString()); ClientResponse response = _vplexApiClient.get(requestURI); String responseStr = response.getEntity(String.class); response.close(); s_logger.info("getDistributedDeviceComponents name response is {}", responseStr); List<VPlexResourceInfo> componentList = VPlexApiUtils.getChildrenFromResponse( VPlexApiConstants.URI_DISTRIBUTED_DEVICES + ddInfo.getName() + VPlexApiConstants.URI_DISTRIBUTED_DEVICE_COMP, responseStr, VPlexResourceInfo.class); return componentList; } /** * Rename a VPlex resource that subclasses VPlexResourceInfo. * Adjusts the path and name in the resourceInfo and returns the original object. * * @param resourceInfo - VPlexVirtualVolumeInfo, VPlexDistributedDeviceInfo, etc. * @param newName String -- the desired new name. * @return -- The original VPlexResourceInfo with updated path and name * @throws VPlexApiException */ <T extends VPlexResourceInfo> T renameVPlexResource(T resourceInfo, String newName) throws VPlexApiException { StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(VPlexApiConstants.VPLEX_PATH); pathBuilder.append(resourceInfo.getPath()); pathBuilder.append("?"); pathBuilder.append(VPlexApiConstants.ATTRIBUTE_NAME_JSON_KEY); pathBuilder.append("="); pathBuilder.append(newName); URI requestURI = _vplexApiClient.getBaseURI().resolve( URI.create(pathBuilder.toString())); s_logger.info("Update name URI is {}", requestURI.toString()); // Try and rename the resource. For newly created resources, the VPLEX // database may not be consistent and the VPLEX may return an unexpected // failure (See COP-24456). A retry of the request is likely to succeed. int retryCount = 0; while (++retryCount <= VPlexApiConstants.RENAME_RESOURCE_MAX_TRIES) { ClientResponse response = _vplexApiClient.put(requestURI); String responseStr = response.getEntity(String.class); s_logger.info("Update name response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Update name is completing asynchronously"); _vplexApiClient.waitForCompletion(response); response.close(); } else { response.close(); if (retryCount == VPlexApiConstants.RENAME_RESOURCE_MAX_TRIES) { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.renameResourceFailureStatus( String.valueOf(response.getStatus()), cause); } else { s_logger.info(String.format("Update name for resource %s failed on attempt %d of %d, retrying...", resourceInfo.getName(), retryCount, VPlexApiConstants.RENAME_RESOURCE_MAX_TRIES)); VPlexApiUtils.pauseThread(VPlexApiConstants.RENAME_RESOURCE_SLEEP_TIME_MS); continue; } } } String newPath = resourceInfo.getPath().replaceFirst(resourceInfo.getName(), newName); resourceInfo.setPath(newPath); resourceInfo.setName(newName); break; } return resourceInfo; } /** * All cached data associated with the virtual volume with the passed name * is invalidated on all directors of the VPLEX cluster. Subsequent reads * from host applications will fetch data from storage volume due to cache * miss. * * NOTE: According to VPLEX engineering, the execution of the cache-invalidate * command will wait 5 minutes for the invalidation to complete. If it does * not complete successfully or in error within this time, the command will * return a failure status, but return a message in the response "exception" * field indicating that the invalidate is still in progress. In this case, * the cache-invalidate-status command must be executed to monitor the * progress until it completes. * * @param virtualVolumeName The name of the virtual volume. * * @return true if the invalidate cache is still in progress when the * call returns and the status must be monitored until it completes, * false otherwise. * * @throws VPlexApiException When an error occurs invalidating the cache. */ public boolean invalidateVirtualVolumeCache(String virtualVolumeName) throws VPlexApiException { s_logger.info("Request to invalidate virtual volume cache for volume {}", virtualVolumeName); ClientResponse response = null; try { // Find the virtual volume. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexVirtualVolumeInfo virtualVolumeInfo = discoveryMgr .findVirtualVolume(virtualVolumeName, false); // Invalidate the read cache for the virtual volume. URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_INVALIDATE_VOLUME_CACHE); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_V, virtualVolumeInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Invalidate cache POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Invalidate cache response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Invalidate cache completing asynchrounously"); _vplexApiClient.waitForCompletion(response); return false; } else { // For a failure status, the exception message will indicate // if failure is because the cache invalidation timed out and // is still in progress. If it is not, then it failed. String exceptionMessage = VPlexApiUtils.getExceptionMessageFromResponse(responseStr); if ((exceptionMessage != null) && (exceptionMessage.contains(VPlexApiConstants.CACHE_INVALIDATE_IN_PROGRESS_MSG))) { return true; } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.invalidateCacheFailureStatus( virtualVolumeName, String.valueOf(response.getStatus()), cause); } } } return false; } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedInvalidatingVolumeCache(virtualVolumeName, e); } finally { if (response != null) { response.close(); } } } /** * Get the cache invalidate status information for the virtual volume with * the passed name. * * @param virtualVolumeName The name of the virtual volume. * * @return A reference to a VPlexCacheStatusInfo specifying the cache status * information. */ public VPlexCacheStatusInfo getCacheStatus(String virtualVolumeName) throws VPlexApiException { s_logger.info("Request to get the cache status for for volume {}", virtualVolumeName); VPlexCacheStatusInfo cacheStatusInfo = new VPlexCacheStatusInfo(); ClientResponse response = null; try { // Find the virtual volume. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexVirtualVolumeInfo virtualVolumeInfo = discoveryMgr .findVirtualVolume(virtualVolumeName, false); // Get the cache invalidate status. URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_INVALIDATE_VOLUME_CACHE_STATUS); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_V, virtualVolumeInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Get cache status POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Get cache status response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Get cache status completing asynchrounously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.getCacheStatusFailureStatus( virtualVolumeName, String.valueOf(response.getStatus()), cause); } } // Successful response. Parse the custom-data from the response, which // contains the information to determine the progress of the cache // invalidation. Update the cache status info invalidation status and if // it is determined that the cache invalidation failed, set the failure // error message. // // NOTE: TBD once I know how to parse this info. cacheStatusInfo.setCacheInvalidateStatus(InvalidateStatus.SUCCESS); return cacheStatusInfo; } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedGettingCacheStatus(virtualVolumeName, e); } finally { if (response != null) { response.close(); } } } /** * Detaches the mirror specified by the passed mirror info from the * local VPLEX volume with the passed name. * * @param virtualVolumeName The name of the VPLEX Local volume. * @param mirrorDeviceName The name of the mirror to be detached. * @param discard The boolean value for discard * * @throws VPlexApiException When an error occurs detaching the mirror from the volume. */ public void detachMirrorFromLocalVirtualVolume(String virtualVolumeName, String mirrorDeviceName, boolean discard) throws VPlexApiException { s_logger.info("Request to detach mirror {} from a virtual volume {}", mirrorDeviceName, virtualVolumeName); ClientResponse response = null; try { // Find the virtual volume to make sure it exists. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); // Find the virtual volume. VPlexVirtualVolumeInfo virtualVolumeInfo = findVirtualVolumeAndUpdateInfo(virtualVolumeName, discoveryMgr); // Find the source device for the virtual volume. String sourceDeviceName = virtualVolumeInfo.getSupportingDevice(); VPlexDeviceInfo sourceDeviceInfo = discoveryMgr.findLocalDevice(sourceDeviceName); if (sourceDeviceInfo == null) { throw VPlexApiException.exceptions .cantFindLocalDeviceForVolume(virtualVolumeName); } // Get components from the source volume device to check the mirror to be detached // exist there. String mirrorDevicePath = null; List<VPlexLocalDeviceComponentInfo> components = discoveryMgr.getLocalDeviceComponents(sourceDeviceInfo); for (VPlexLocalDeviceComponentInfo component : components) { if (component.getName().equals(mirrorDeviceName)) { mirrorDevicePath = component.getPath(); break; } } // Throw an exception if we can't find device component // corresponding to the mirror to be updated. if (mirrorDevicePath == null) { throw VPlexApiException.exceptions.cantFindMirror(mirrorDeviceName, virtualVolumeName); } // Detach this mirror device component from the source device. URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DEVICE_DETACH_MIRROR); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, sourceDeviceInfo.getPath()); argsMap.put(VPlexApiConstants.ARG_DASH_M, mirrorDevicePath); if (discard) { // If discard is false then without discard option the device will be // converted into virtual volume. argsMap.put(VPlexApiConstants.ARG_DISCARD, ""); } JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Detach mirror for virtual volume POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Detach mirror for virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Detach mirror for virtual volume is completing asynchrounously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.detachMirrorFailureStatus( mirrorDeviceName, virtualVolumeName, String.valueOf(response.getStatus()), cause); } } // if detach was a success, we need to flatten the device // to align with standard ViPR 1-1-1 structure _vplexApiClient.deviceCollapse(sourceDeviceName, VPlexApiConstants.LOCAL_DEVICE); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDetachingVPlexVolumeMirror( mirrorDeviceName, virtualVolumeName, e); } finally { if (response != null) { response.close(); } } } /** * Detaches the mirror specified by the passed mirror info from the * Distributed VPLEX volume with the passed name. * * @param virtualVolumeName The name of the VPLEX Local volume. * @param mirrorDeviceName The name of the mirror to be detached. * @param discard The boolean value for discard * * @throws VPlexApiException When an error occurs detaching the mirror from the volume. */ public void detachLocalMirrorFromDistributedVirtualVolume(String virtualVolumeName, String mirrorDeviceName, boolean discard) throws VPlexApiException { s_logger.info("Request to detach mirror {} from a virtual volume {}", mirrorDeviceName, virtualVolumeName); try { // Find the virtual volume to make sure it exists. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexVirtualVolumeInfo virtualVolumeInfo = findVirtualVolumeAndUpdateInfo(virtualVolumeName, discoveryMgr); // Find the source device for the virtual volume. String sourceDeviceName = virtualVolumeInfo.getSupportingDevice(); // Find the distributed device VPlexDistributedDeviceInfo distributedDeviceInfo = discoveryMgr.findDistributedDevice(sourceDeviceName); if (distributedDeviceInfo == null) { throw VPlexApiException.exceptions.cantFindDistDevice(sourceDeviceName); } String sourceDevicePath = null; String mirrorDevicePath = null; List<VPlexDistributedDeviceComponentInfo> ddComponents = discoveryMgr .getDistributedDeviceComponents(distributedDeviceInfo); for (VPlexDistributedDeviceComponentInfo ddComponent : ddComponents) { discoveryMgr.updateDistributedDeviceComponent(ddComponent); List<VPlexLocalDeviceComponentInfo> localComponents = discoveryMgr.getLocalDeviceComponents(ddComponent); for (VPlexLocalDeviceComponentInfo localComponent : localComponents) { if (localComponent.getName().equals(mirrorDeviceName)) { sourceDevicePath = ddComponent.getPath(); mirrorDevicePath = localComponent.getPath(); break; } } if (sourceDevicePath != null && mirrorDevicePath != null) { break; } } // Throw an exception if we can't find device component // corresponding to the mirror to be updated. if (mirrorDevicePath == null) { throw VPlexApiException.exceptions.cantFindMirror(mirrorDeviceName, virtualVolumeName); } // Detach this mirror device component from the source device. URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DEVICE_DETACH_MIRROR); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, sourceDevicePath); argsMap.put(VPlexApiConstants.ARG_DASH_M, mirrorDevicePath); if (discard) { // If discard is false then without discard option the device will be // converted into virtual volume. argsMap.put(VPlexApiConstants.ARG_DISCARD, ""); } JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Detach mirror for virtual volume POST data is {}", postDataObject.toString()); ClientResponse response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Detach mirror for virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Detach mirror for virtual volume is completing asynchrounously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.detachMirrorFailureStatus( mirrorDeviceName, virtualVolumeName, String.valueOf(response.getStatus()), cause); } } // if detach was a success, we need to flatten the device // to align with standard ViPR 1-1-1 structure _vplexApiClient.deviceCollapse(sourceDevicePath, VPlexApiConstants.COLLAPSE_BY_PATH); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDetachingVPlexVolumeMirror( mirrorDeviceName, virtualVolumeName, e); } } /** * Detaches the mirror on the specified cluster from the distributed VPLEX * volume with the passed name. * * @param virtualVolumeName The name of the VPLEX distributed volume. * @param clusterId The cluster of the mirror to detach. * * @return The name of the detached mirror for use when reattaching the mirror. * * @throws VPlexApiException When an error occurs detaching the mirror from the volume. */ public String detachMirrorFromDistributedVolume(String virtualVolumeName, String clusterId) throws VPlexApiException { s_logger.info("Request to detach mirror on cluster {} from a distributed volume {}", clusterId, virtualVolumeName); ClientResponse response = null; try { // Find the virtual volume to make sure it exists. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexVirtualVolumeInfo virtualVolumeInfo = findVirtualVolumeAndUpdateInfo(virtualVolumeName, discoveryMgr); // Find the distributed device for the virtual volume. String ddName = virtualVolumeInfo.getSupportingDevice(); VPlexDistributedDeviceInfo ddInfo = discoveryMgr.findDistributedDevice(ddName); if (ddInfo == null) { throw VPlexApiException.exceptions .cantFindDistributedDeviceForVolume(virtualVolumeName); } // Get the distributed device components. These are the local devices // that were used to build the distributed device. Then find the // component corresponding to the mirror to be detached. String mirrorDevicePath = null; String detachedDeviceName = null; List<VPlexDistributedDeviceComponentInfo> ddComponents = discoveryMgr .getDistributedDeviceComponents(ddInfo); for (VPlexDistributedDeviceComponentInfo ddComponent : ddComponents) { discoveryMgr.updateDistributedDeviceComponent(ddComponent); if (ddComponent.getCluster().equals(clusterId)) { mirrorDevicePath = ddComponent.getPath(); detachedDeviceName = ddComponent.getName(); s_logger.info("Detached device is {}", detachedDeviceName); break; } } // Throw an exception if we can't find distributed device component // corresponding to the mirror to be updated. if (mirrorDevicePath == null) { throw VPlexApiException.exceptions.cantFindMirrorForDetach(clusterId, virtualVolumeName); } // Detach this component from the distributed device. URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DEVICE_DETACH_MIRROR); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, ddInfo.getPath()); argsMap.put(VPlexApiConstants.ARG_DASH_M, mirrorDevicePath); argsMap.put(VPlexApiConstants.ARG_DISCARD, ""); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Detach mirror for virtual volume POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Detach mirror for virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Detach mirror for virtual volume is completing asynchrounously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.detachMirrorFailureStatus( clusterId, virtualVolumeName, String.valueOf(response.getStatus()), cause); } } s_logger.info("Detached device is {}", detachedDeviceName); return detachedDeviceName; } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDetachingVPlexVolumeMirror( clusterId, virtualVolumeName, e); } finally { if (response != null) { response.close(); } } } /** * Reattach the mirror on the specified cluster to the distributed VPLEX * volume with the passed name. * * @param virtualVolumeName The name of the VPLEX distributed volume. * @param detachedDeviceName The local device name of the mirror previously detached. * * @throws VPlexApiException When an error occurs reattaching the mirror to * the volume. */ public void reattachMirrorToDistributedVolume(String virtualVolumeName, String detachedDeviceName) throws VPlexApiException { s_logger.info("Request to reattach mirror {} to distributed volume {}", detachedDeviceName, virtualVolumeName); try { // Find the virtual volume to make sure it exists. VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); VPlexVirtualVolumeInfo vplexVirtualVolumeInfo = findVirtualVolumeAndUpdateInfo(virtualVolumeName, discoveryMgr); // Find the distributed device for the virtual volume. String ddName = vplexVirtualVolumeInfo.getSupportingDevice(); VPlexDistributedDeviceInfo ddInfo = discoveryMgr.findDistributedDevice(ddName); if (ddInfo == null) { throw VPlexApiException.exceptions .cantFindDistributedDeviceForVolume(virtualVolumeName); } // Find the local device for the passed mirror. Note that when // the mirror is detached from the distributed device, the // local devices that comprise the distributed device are no // longer listed as components of the distributed device. We // must find the local device on the cluster on which it resides. VPlexDeviceInfo mirrorDeviceInfo = discoveryMgr.findLocalDevice(detachedDeviceName); if (mirrorDeviceInfo == null) { throw VPlexApiException.exceptions.cantFindMirrorForAttach( detachedDeviceName, virtualVolumeName); } String mirrorDevicePath = mirrorDeviceInfo.getPath(); // Reattach this local device to the distributed device. URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DEVICE_ATTACH_MIRROR); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, ddInfo.getPath()); argsMap.put(VPlexApiConstants.ARG_DASH_M, mirrorDevicePath); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, true); s_logger.info("Reattach mirror for virtual volume POST data is {}", postDataObject.toString()); // We'll retry a failed reattach in case some transitive issue // caused the reattach to fail. We want to do our best to recreate // the distributed volume. int reattachTryCount = 0; while (++reattachTryCount <= VPlexApiConstants.REATTACH_HA_MIRROR_RETRY_COUNT) { ClientResponse response = null; try { response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Attach mirror for virtual volume response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Attach mirror for virtual volume is completing asynchrounously"); _vplexApiClient.waitForCompletion(response); // Reattach succeeded upon waiting for completion. break; } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.reattachMirrorFailureStatus( detachedDeviceName, virtualVolumeName, String.valueOf(response.getStatus()), cause); } } // Reattach succeeded. break; } catch (Exception e) { if (reattachTryCount == VPlexApiConstants.REATTACH_HA_MIRROR_RETRY_COUNT) { if (e instanceof VPlexApiException) { throw e; } else { throw VPlexApiException.exceptions.failedAttachingVPlexVolumeMirror( detachedDeviceName, virtualVolumeName, e); } } else { VPlexApiUtils.pauseThread(VPlexApiConstants.REATTACH_HA_MIRROR_SLEEP_TIME_MS); } } finally { if (response != null) { response.close(); } } } } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedAttachingVPlexVolumeMirror( detachedDeviceName, virtualVolumeName, e); } } /** * This method finds virtual volume on the VPLEX and then updates virtual volume info. * * @param virtualVolumeName virtual volume name * @param discoveryMgr reference to VPlexApiDiscoveryManager * @return VPlexVirtualVolumeInfo object with updated info. */ public VPlexVirtualVolumeInfo findVirtualVolumeAndUpdateInfo(String virtualVolumeName, VPlexApiDiscoveryManager discoveryMgr) { VPlexVirtualVolumeInfo virtualVolumeInfo = null; if (null == discoveryMgr) { discoveryMgr = _vplexApiClient.getDiscoveryManager(); } List<VPlexClusterInfo> clusterInfoList = discoveryMgr.getClusterInfoLite(); for (VPlexClusterInfo clusterInfo : clusterInfoList) { String clusterName = clusterInfo.getName(); virtualVolumeInfo = discoveryMgr.findVirtualVolume(clusterName, virtualVolumeName, false); if (virtualVolumeInfo != null) { discoveryMgr.updateVirtualVolumeInfo(clusterName, virtualVolumeInfo); break; } } if (virtualVolumeInfo == null) { throw VPlexApiException.exceptions.cantFindRequestedVolume(virtualVolumeName); } return virtualVolumeInfo; } /** * This method collapses the one legged device for the passed virtual volume device. * After this device will change back to local device. * * @param sourceDeviceNameOrPath source device name or path * @param collapseType "local" or "distributed" or "collapse-by-path" */ public void deviceCollapse(String sourceDeviceNameOrPath, String collapseType) { ClientResponse response = null; try { VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); // Find the source device. VPlexResourceInfo sourceDevice = null; String devicePath = null; if (VPlexApiConstants.DISTRIBUTED_DEVICE.equalsIgnoreCase(collapseType)) { sourceDevice = discoveryMgr.findDistributedDevice(sourceDeviceNameOrPath); if (sourceDevice != null) { devicePath = sourceDevice.getPath(); } } else if (VPlexApiConstants.LOCAL_DEVICE.equalsIgnoreCase(collapseType)) { sourceDevice = discoveryMgr.findLocalDevice(sourceDeviceNameOrPath); if (sourceDevice != null) { devicePath = sourceDevice.getPath(); } } else if (VPlexApiConstants.COLLAPSE_BY_PATH.equalsIgnoreCase(collapseType)){ devicePath = sourceDeviceNameOrPath; } else { throw new Exception("invalid collapse type: " + collapseType); } if (devicePath == null) { throw VPlexApiException.exceptions.cantFindLocalDevice(sourceDeviceNameOrPath); } s_logger.info("Found the device path to collapse {}", devicePath); URI requestURI = _vplexApiClient.getBaseURI().resolve( VPlexApiConstants.URI_DEVICE_COLLAPSE); s_logger.info("Device collapse URI is {}", requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.ARG_DASH_D, devicePath); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Device collapse POST data is {}", postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Device collapse response is {}", responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Device collapse completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.failedDeviceCollapseStatus( sourceDeviceNameOrPath, String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully did device collapse for device {}", devicePath); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedDeviceCollapse(sourceDeviceNameOrPath, e); } finally { if (response != null) { response.close(); } } } /** * This method sets device visibility to local. * * @param sourceDeviceName source device name * @throws VPlexApiException */ public void setDeviceVisibility(String sourceDeviceName) { ClientResponse response = null; try { VPlexApiDiscoveryManager discoveryMgr = _vplexApiClient.getDiscoveryManager(); // Find the source local device. VPlexDeviceInfo sourceLocalDevice = discoveryMgr.findLocalDevice(sourceDeviceName); if (sourceLocalDevice == null) { throw VPlexApiException.exceptions.cantFindLocalDevice(sourceDeviceName); } String devicePath = sourceLocalDevice.getPath(); s_logger.info("Found the local device {}", devicePath); // Build the request path. StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append(VPlexApiConstants.VPLEX_PATH); pathBuilder.append(devicePath); pathBuilder.append(VPlexApiConstants.QUESTION_MARK); pathBuilder.append(VPlexApiConstants.ATTRIBUTE_DEVICE_VISIBILITY); pathBuilder.append(VPlexApiConstants.EQUALS); pathBuilder.append(VPlexApiConstants.LOCAL_DEVICE); URI requestURI = _vplexApiClient.getBaseURI().resolve( URI.create(pathBuilder.toString())); s_logger.info("Set device visibility URI is {}", requestURI.toString()); response = _vplexApiClient.put(requestURI); String responseStr = response.getEntity(String.class); s_logger.info("Set device visibility response is {}", responseStr); int status = response.getStatus(); if (status != VPlexApiConstants.SUCCESS_STATUS) { if (status == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Set device visibility is completing asynchronously"); _vplexApiClient.waitForCompletion(response); response.close(); } else { response.close(); String cause = VPlexApiUtils.getCauseOfFailureFromResponse(responseStr); throw VPlexApiException.exceptions.failedSettingDeviceVisibilityStatus( sourceDeviceName, String.valueOf(response.getStatus()), cause); } } s_logger.info("Successfully set {} visibility for device {}", VPlexApiConstants.LOCAL_DEVICE, devicePath); } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedSettingDeviceVisibility(sourceDeviceName, e); } finally { if (response != null) { response.close(); } } } /** * Sends a request to the VPLEX to turn on thin provisioning for the given * virtual volume. Will return a boolean value indicating whether or not * the request was a success. * * @param virtualVolumeInfo the virtual volume to update * @return true if the thin-enabled request was a success */ public boolean setVirtualVolumeThinEnabled(VPlexVirtualVolumeInfo virtualVolumeInfo) { ClientResponse response = null; try { // set thin enabled to false by default on the out-param until we get a successful response virtualVolumeInfo.setThinEnabled(VPlexApiConstants.FALSE); s_logger.info("Requesting thin-enabled flag set to true on virtual volume " + virtualVolumeInfo.getName()); URI requestURI = _vplexApiClient.getBaseURI().resolve(VPlexApiConstants.URI_SET_THIN_ENABLED_VIRTUAL_VOLUME); s_logger.info("Set thin-enabled virtual-volume URI is " + requestURI.toString()); Map<String, String> argsMap = new HashMap<String, String>(); argsMap.put(VPlexApiConstants.TRUE.toString(), ""); argsMap.put(VPlexApiConstants.ARG_VIRTUAL_VOLUMES, virtualVolumeInfo.getPath()); JSONObject postDataObject = VPlexApiUtils.createPostData(argsMap, false); s_logger.info("Set thin-enabled virtual-volume POST data is " + postDataObject.toString()); response = _vplexApiClient.post(requestURI, postDataObject.toString()); String responseStr = response.getEntity(String.class); s_logger.info("Set thin-enabled virtual-volume response is " + responseStr); if (response.getStatus() != VPlexApiConstants.SUCCESS_STATUS) { if (response.getStatus() == VPlexApiConstants.ASYNC_STATUS) { s_logger.info("Set thin-enabled virtual-volume command completing asynchronously"); _vplexApiClient.waitForCompletion(response); } else { s_logger.info("set-thin-enabled command was not successful on virtual volume " + virtualVolumeInfo.getName()); return false; } } // if it got this far, we can assume thin-enabled and thin-capable are both true virtualVolumeInfo.setThinEnabled(VPlexApiConstants.TRUE); virtualVolumeInfo.setThinCapable(VPlexApiConstants.TRUE); s_logger.info("Successfully executed set-thin-enabled command"); return true; } catch (VPlexApiException vae) { throw vae; } catch (Exception e) { throw VPlexApiException.exceptions.failedSettingThinEnabled(virtualVolumeInfo.getName(), e); } finally { if (response != null) { response.close(); } } } }