/*
* 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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.vplex.api.VPlexVirtualVolumeInfo.WaitOnRebuildResult;
import com.emc.storageos.vplex.api.clientdata.PortInfo;
import com.emc.storageos.vplex.api.clientdata.VolumeInfo;
import com.sun.jersey.api.client.ClientResponse;
/**
* The VPlex API Client is used to get information from the VPlex and to execute
* configuration commands on a VPlex. A VPlexApiClient instance represents a
* connection to a single VPlex management server. Use the {@link VPlexApiFactory} to get a VPlexApiClient instance for a given VPlex
* management server.
*
* NOTE: The Jersey client releases http connections automatically in two cases:
*
* 1. When the ClientResponse does not have entity.
* 2. When the ClientResponse does have an entity and it was read from response.
*
* In case when the ClientResponse has an entity and the entity is not read,
* the Jersey client does not release the connection (see JavaDoc for Jersey's
* ApacheHttpClientHandler.java), and the connection is not returned back to
* connection pool to be available for a new request.
*
* Therefore, it is incumbent upon programmer's modifying this class and its
* associated manager classes to add new requests to ensure that the entity
* is read from the response or that the connection is closed in a finally
* block. Currently, the former is the approach taken, as the entity for each
* response from the VPLEX is read and logged so we know the content of the
* response from the server.
*/
public class VPlexApiClient {
// The number of times a get request will be retried
// and the wait interval between attempts.
public static final int GET_RETRY_COUNT = 12;
public static final long GET_SLEEP_TIME_MS = 5000;
// Logger reference.
private static Logger s_logger = LoggerFactory.getLogger(VPlexApiClient.class);
// The base URI of the VPlex Management Station.
private URI _baseURI;
// The REST client for executing requests to the VPlex Management Station.
private RESTClient _client;
// A reference to the discovery manager.
private VPlexApiDiscoveryManager _discoveryMgr;
// A reference to the virtual volume manager.
private VPlexApiVirtualVolumeManager _virtualVolumeMgr;
// A reference to the export manager.
private VPlexApiExportManager _exportMgr;
// A reference to the migration manager.
private VPlexApiMigrationManager _migrationMgr;
// A reference to the consistency group manager manager.
private VPlexApiConsistencyGroupManager _cgMgr;
// The ID of the VPLEX session to pass in requests to the
// VPLEX management server associated with this client. Is
// set on the first request and updated on every response
// in case it expires and a new session is created.
private String _vplexSessionId = null;
// caching of some almost-static VPLEX info for performance improvement
private volatile Map<String, String> _vplexClusterIdToNameCache = new HashMap<String, String>();
private volatile List<VPlexClusterInfo> _vplexClusterInfoLiteCache = new ArrayList<VPlexClusterInfo>();
private volatile Map<String, Map<String, String>> _vplexClusterInitiatorWwnToNameCache = new HashMap<String, Map<String, String>>();
/**
* Constructor
*
* @param endpoint The URI of the VPlex Management Station.
* @param client A reference to the REST client for making requests.
*/
VPlexApiClient(URI endpoint, RESTClient client) {
_baseURI = endpoint;
_client = client;
_discoveryMgr = new VPlexApiDiscoveryManager(this);
_virtualVolumeMgr = new VPlexApiVirtualVolumeManager(this);
_exportMgr = new VPlexApiExportManager(this);
_migrationMgr = new VPlexApiMigrationManager(this);
_cgMgr = new VPlexApiConsistencyGroupManager(this);
}
/**
* Verifies the client can connect to the VPLEX to identify issues
* in the client connectivity information such as IP address, port,
* username, and password.
*/
public void verifyConnectivity() {
// Attempt to make a valid request to the VPLEX management server.
URI requestURI = _baseURI.resolve(VPlexApiConstants.URI_CLUSTERS);
ClientResponse response = null;
try {
response = get(requestURI);
String responseStr = response.getEntity(String.class);
s_logger.info("Verify connectivity response is {}", responseStr);
if (responseStr == null || responseStr.equals("")) {
s_logger.error("Response from VPLEX was empty.");
throw VPlexApiException.exceptions.connectionFailure(_baseURI.toString());
}
int responseStatus = response.getStatus();
if (responseStatus != VPlexApiConstants.SUCCESS_STATUS) {
s_logger.info("Verify connectivity response status is {}", responseStatus);
if (responseStatus == VPlexApiConstants.AUTHENTICATION_STATUS) {
// Bad user name and/or password.
throw VPlexApiException.exceptions.authenticationFailure(_baseURI.toString());
} else {
// Could be a 404 because the IP was not that for a VPLEX.
throw VPlexApiException.exceptions.connectionFailure(_baseURI.toString());
}
}
} catch (VPlexApiException vae) {
throw vae;
} catch (Exception e) {
// Could be a java.net.ConnectException for an invalid IP address
// or a java.net.SocketTimeoutException for a bad port.
throw VPlexApiException.exceptions.connectionFailure(_baseURI.toString());
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Returns the version of the VPlex management software.
*
* @return The version of the VPlex management software.
*
* @throws VPlexApiException When an error occurs getting the version information.
*/
public String getManagementSoftwareVersion() throws VPlexApiException {
s_logger.info("Request for management software version for VPlex at {}", _baseURI);
return _discoveryMgr.getManagementSoftwareVersion();
}
/**
* Determines the serial number of the VPlex management server.
*
* @return The serial number of the VPlex management server.
*
* @throws VPlexApiException When an error occurs querying the VPlex.
*/
public String getManagementServerSerialNumber() throws VPlexApiException {
s_logger.info("Request for management server serial number for VPlex at {}", _baseURI);
return _discoveryMgr.getManagementServerSerialNumber();
}
/**
* Gets detailed information about the VPLEX clusters, including storage
* volume information. This method will always call the VPLEX API to get
* the very latest info, bypassing the local cluster info cache.
*
* @return A list of VPlexClusterInfo instances.
*
* @throws VPlexApiException When an error occurs querying the VPlex.
*/
public List<VPlexClusterInfo> getClusterInfoDetails() throws VPlexApiException {
s_logger.info("Request for complete detailed cluster info for VPlex at {}", _baseURI);
return _discoveryMgr.getClusterInfo(false, true, null);
}
/**
* Gets information about the VPLEX clusters. The "lite" version
* fetches all information except for the deep storage volume information,
* which is a very expensive call only needed in certain cases. If that
* information is needed, use VPlexApiClient.getClusterInformationDetails.
*
* Additionally this "lite" method will retrieve the cluster info from a
* local cache in order to improve performance.
*
* @return A list of VPlexClusterInfo instances.
*
* @throws VPlexApiException When an error occurs querying the VPlex.
*/
public synchronized List<VPlexClusterInfo> getClusterInfoLite() throws VPlexApiException {
s_logger.info("Request for lightweight cluster info for VPlex at {}", _baseURI);
if (_vplexClusterInfoLiteCache.isEmpty()) {
_vplexClusterInfoLiteCache.addAll(_discoveryMgr.getClusterInfo(true, false, null));
s_logger.info("refreshed lightweight cluster info list is " + _vplexClusterInfoLiteCache.toString());
}
// return a copy
return new ArrayList<VPlexClusterInfo>(_vplexClusterInfoLiteCache);
}
/**
* Gets a VPlexClusterInfo from the cache for the given VPLEX cluster id.
*
* @param vplexClusterName the name of the VPLEX cluster to get.
* @return a VPlexClusterInfo instance for the requested cluster id, or null if not found.
*
* @throws VPlexApiException When an error occurs querying the VPlex.
*/
public VPlexClusterInfo getClusterInfoLiteForClusterName(String vplexClusterName) throws VPlexApiException {
for (VPlexClusterInfo clusterInfo : getClusterInfoLite()) {
if (clusterInfo != null && clusterInfo.getName() != null && clusterInfo.getName().equals(vplexClusterName)) {
return clusterInfo;
}
}
s_logger.error("VPlexClusterInfo for cluster id {} not found.", vplexClusterName);
throw VPlexApiException.exceptions.couldNotFindCluster(vplexClusterName);
}
/**
* Rediscovers the storage systems attached to the VPlex identified by the
* passed identifiers for the purpose of discovering new volumes accessible
* to the VPlex.
*
* @param storageSystemNativeGuids The native guids of the storage systems
* to be rediscovered.
*/
public void rediscoverStorageSystems(List<String> storageSystemNativeGuids)
throws VPlexApiException {
s_logger.info("Request to rediscover storage systems on VPlex at {}", _baseURI);
_discoveryMgr.rediscoverStorageSystems(storageSystemNativeGuids);
}
/**
* Gets the information for the storage systems accessible by the VPlex.
*
* @return A list of VPlexStorageSystemInfo specifying the info for the
* storage systems accessible to the VPlex.
*
* @throws VPlexApiException If a VPlex request returns a failed status or
* an error occurs processing the response.
*/
public List<VPlexStorageSystemInfo> getStorageSystemInfo() throws VPlexApiException {
s_logger.info("Request for storage system info for VPlex at {}", _baseURI);
return _discoveryMgr.getStorageSystemInfo();
}
/**
* Gets the information for the VPlex Ports.
*
* @param shallow if true, the director info will not be fetched (just the PortInfo data),
* otherwise, we will traverse engines and directors assembling complete list
* @return A list of VPlexPortInfo specifying the info for the VPlex ports.
*
* @throws VPlexApiException If a VPlex request returns a failed status or
* an error occurs processing the response.
*/
public List<VPlexPortInfo> getPortInfo(boolean shallow) throws VPlexApiException {
s_logger.info("Request for port info for VPlex at {}", _baseURI);
if (shallow) {
// gets all with API URI wildcards
return _discoveryMgr.getPortInfo();
} else {
// traverses engines and directors assembling complete list
return _discoveryMgr.getPortAndDirectorInfo();
}
}
/**
* Gets the fully-detailed VPLEX storage view information, including detailed initiator
* data. The initiator data can require a lot of extra processing, so if this information
* is not needed by the caller, VPlexApiClient.getStorageViewsLite may be a sufficient and
* much faster option.
*
* @return A list of VPlexStorageViewInfo specifying the info for the VPLEX storage views.
*
* @throws VPlexApiException If a VPlex request returns a failed status or
* an error occurs processing the response.
*/
public List<VPlexStorageViewInfo> getStorageViewDetails() throws VPlexApiException {
s_logger.info("Request for detailed storage view info for VPlex at {}", _baseURI);
return _discoveryMgr.getStorageViews(true);
}
/**
* Gets a "lite" version of the VPLEX storage view data with only the top level properties
* included to improve performance. This means that the storage view info objects will not
* include detailed initiator information (which is very expensive to populate, requiring
* many VPLEX API calls). If the initiator information is needed, use the
* VPlexApiClient.getStorageViewDetails method instead.
*
* @return A list of VPlexStorageViewInfo specifying the info for the VPLEX storage views.
*
* @throws VPlexApiException If a VPLEX request returns a failed status or
* an error occurs processing the response.
*/
public List<VPlexStorageViewInfo> getStorageViewsLite() throws VPlexApiException {
s_logger.info("Request for lightweight storage view info for VPlex at {}", _baseURI);
return _discoveryMgr.getStorageViews(false);
}
/**
* Gets the fully-populated VPlexStorageViewInfo object for the given storage view name
* on the given VPLEX cluster (by cluster name, not cluster id).
*
* @param clusterName the cluster name for storage view scope
* @param storageViewName the name of the storage view to get
* @return a VPlexStorageViewInfo object for the storage view name and VPLEX cluster location
* @throws VPlexApiException
*/
public VPlexStorageViewInfo getStorageView(String clusterName, String storageViewName)
throws VPlexApiException {
s_logger.info("Request to get storage view on VPlex at {}", _baseURI);
return _discoveryMgr.getStorageView(clusterName, storageViewName);
}
/**
* Gets all virtual volumes on the VPLEX storage system.
*
* @param shallow When true does a shallow discovery of the virtual volumes. A
* shallow discovery simple gets the name and context path for each virtual
* volume. A deep discovery will discover the component structure of the virtual
* volumes including the distributed devices, local devices, extents, and storage
* volumes used to construct the virtual volume. A deep discovery is far more
* expensive as many requests must be made to the VPLEX to discovery the structure
* for each volume. A VPLEX with thousands of volumes could requires 10's or 100's
* of thousands of requests.
*
* @return A list of VPlexVirtualVolumeInfo for the virtual volumes on the VPLEX.
*
* @throws VPlexApiException When an error occurs getting the volumes.
*/
public Map<String, VPlexVirtualVolumeInfo> getVirtualVolumes(boolean shallow) throws VPlexApiException {
s_logger.info("Request for {} discovery of virtual volume info for VPlex at {}",
(shallow ? "shallow" : "deep"), _baseURI);
Map<String, VPlexVirtualVolumeInfo> virtualVolumeInfoMap = new HashMap<String, VPlexVirtualVolumeInfo>();
Map<String, VPlexVirtualVolumeInfo> distributedVirtualVolumesMap = new HashMap<String, VPlexVirtualVolumeInfo>();
Map<String, Map<String, VPlexVirtualVolumeInfo>> localVirtualVolumesMap = new HashMap<String, Map<String, VPlexVirtualVolumeInfo>>();
// Get the cluster information.
List<VPlexClusterInfo> clusterInfoList = getClusterInfoLite();
for (VPlexClusterInfo clusterInfo : clusterInfoList) {
String clusterId = clusterInfo.getName();
// for each cluster get the virtual volume information.
List<VPlexVirtualVolumeInfo> clusterVirtualVolumeInfoList = _discoveryMgr.getVirtualVolumesForCluster(clusterId);
for (VPlexVirtualVolumeInfo virtualVolumeInfo : clusterVirtualVolumeInfoList) {
virtualVolumeInfo.addCluster(clusterId);
String virtualVolumeName = virtualVolumeInfo.getName();
if (!virtualVolumeInfoMap.containsKey(virtualVolumeName)) {
// We want the unique list of virtual volumes on all
// clusters. Distributed volumes will appear on both
// clusters.
virtualVolumeInfoMap.put(virtualVolumeName, virtualVolumeInfo);
// If we are doing a deep discovery of the virtual volumes
// keep a list of the distributed virtual volumes and a list
// of the local virtual volumes for each cluster.
if (!shallow) {
String supportingDeviceName = virtualVolumeInfo.getSupportingDevice();
if (VPlexVirtualVolumeInfo.Locality.distributed.name().equals(
virtualVolumeInfo.getLocality())) {
distributedVirtualVolumesMap.put(supportingDeviceName,
virtualVolumeInfo);
} else {
Map<String, VPlexVirtualVolumeInfo> clusterLocalVolumesMap = localVirtualVolumesMap
.get(clusterId);
if (clusterLocalVolumesMap == null) {
clusterLocalVolumesMap = new HashMap<String, VPlexVirtualVolumeInfo>();
localVirtualVolumesMap.put(clusterId, clusterLocalVolumesMap);
}
clusterLocalVolumesMap.put(supportingDeviceName, virtualVolumeInfo);
}
}
} else if (VPlexVirtualVolumeInfo.Locality.distributed.name().equals(
virtualVolumeInfo.getLocality())) {
// on a distributed volume, we need to be sure to add the second
// cluster id as well... this is needed by ingestion. see CTRL-10982
virtualVolumeInfoMap.get(virtualVolumeName).addCluster(clusterId);
}
}
}
// Do the deep discovery of the component structure for each
// virtual volume, if necessary.
if (!shallow) {
// Get the component structure for each distributed virtual volume
// starting with the supporting distributed device.
_discoveryMgr.setSupportingComponentsForDistributedVirtualVolumes(distributedVirtualVolumesMap);
// Get the component structure for each local virtual volume
// starting with the supporting local device.
for (Map.Entry<String, Map<String, VPlexVirtualVolumeInfo>> mapEntry : localVirtualVolumesMap
.entrySet()) {
_discoveryMgr.setSupportingComponentsForLocalVirtualVolumes(
mapEntry.getKey(), mapEntry.getValue());
}
}
return virtualVolumeInfoMap;
}
/**
* Finds the volume with the passed name and discovers its structure.
*
* @param virtualVolumeName The name of the virtual volume.
*
* @return A VPlexVirtualVolumeInfo containing the volume's structure.
*/
public VPlexVirtualVolumeInfo getVirtualVolumeStructure(String virtualVolumeName) throws VPlexApiException {
VPlexVirtualVolumeInfo vvInfo = _discoveryMgr.findVirtualVolume(virtualVolumeName, true);
VPlexDistributedDeviceInfo ddInfo = _discoveryMgr.findDistributedDevice(vvInfo.getSupportingDevice());
vvInfo.setSupportingDeviceInfo(ddInfo);
_discoveryMgr.setSupportingComponentsForDistributedDevice(ddInfo);
return vvInfo;
}
/**
* 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 searchAllClustersForStorageVolumes If true, search all clusters for backend volumes; If false,
* search for backend volumes will be restricted to the value of winningClusterId
*
* @return The information for the created virtual volume.
*
* @throws VPlexApiException When an error occurs creating the virtual
* volume.
*/
public VPlexVirtualVolumeInfo createVirtualVolume(
List<VolumeInfo> nativeVolumeInfoList, boolean isDistributed,
boolean discoveryRequired, boolean preserveData, String winningClusterId, List<VPlexClusterInfo> clusterInfoList,
boolean findVirtualVolume, boolean thinEnabled, boolean searchAllClustersForStorageVolumes)
throws VPlexApiException {
s_logger.info("Request for virtual volume creation on VPlex at {}", _baseURI);
String clusterName = null;
if (!searchAllClustersForStorageVolumes && (null != winningClusterId)) {
// if all the volumes in the whole request are local volumes,
// we can restrict work to just the local cluster
clusterName = getClusterNameForId(winningClusterId);
}
return _virtualVolumeMgr.createVirtualVolume(nativeVolumeInfoList, isDistributed,
discoveryRequired, preserveData, winningClusterId, clusterInfoList, findVirtualVolume, thinEnabled, clusterName);
}
/**
* 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 When an error occurs renaming resource
*/
public <T extends VPlexResourceInfo> T renameResource(T resourceInfo, String newName)
throws VPlexApiException {
return _virtualVolumeMgr.renameVPlexResource(resourceInfo, newName);
}
/**
* 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
*/
public VPlexDeviceInfo createDeviceAndAttachAsMirror(VPlexVirtualVolumeInfo virtualVolume,
List<VolumeInfo> nativeVolumeInfoList, boolean discoveryRequired, boolean preserveData)
throws VPlexApiException {
s_logger.info("Request for mirror creation on VPlex at {}", _baseURI);
return _virtualVolumeMgr.createDeviceAndAttachAsMirror(virtualVolume, nativeVolumeInfoList, discoveryRequired, preserveData);
}
/**
* Attaches mirror device to the source device
*
* @param locality The constant that tells if it's 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 {
_virtualVolumeMgr.attachMirror(locality, sourceVirtualVolumeName, mirrorDeviceName);
}
/**
* 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.
*/
public void deleteVirtualVolume(List<VolumeInfo> nativeVolumeInfoList)
throws VPlexApiException {
s_logger.info("Request for virtual volume deletion on VPlex at {}", _baseURI);
_virtualVolumeMgr.deleteVirtualVolume(nativeVolumeInfoList);
}
/**
* 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.
*/
public void deleteVirtualVolume(String virtualVolumeName,
boolean unclaimVolumes, boolean retryOnDismantleFailure) throws VPlexApiException {
s_logger.info("Request for virtual volume deletion on VPlex at {}", _baseURI);
_virtualVolumeMgr.deleteVirtualVolume(virtualVolumeName, unclaimVolumes,
retryOnDismantleFailure);
}
/**
* 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.
*/
public void destroyVirtualVolume(String virtualVolumeName) throws VPlexApiException {
s_logger.info("Request for virtual volume destroy on VPlex at {}", _baseURI);
_virtualVolumeMgr.destroyVirtualVolume(virtualVolumeName);
}
/**
* Causes the VPLEX to "forget" about the volumes identified by the
* passed native volume information. Typically called when the calling
* application has deleted backend volumes and wants the VPLEX to disregard
* these volumes.
*
* @param nativeVolumeInfoList The native volume information for the
* storage volumes to be forgotten.
*/
public void forgetVolumes(List<VolumeInfo> nativeVolumeInfoList) throws Exception {
s_logger.info("Request to forget volumes on VPlex at {}", _baseURI);
_discoveryMgr.forgetVolumes(nativeVolumeInfoList);
}
/**
* 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.
*/
public VPlexVirtualVolumeInfo expandVirtualVolume(String virtualVolumeName,
int expansionStatusRetryCount, long expansionStatusSleepTime)
throws VPlexApiException {
s_logger.info("Request for virtual volume expansion on VPlex at {}", _baseURI);
return _virtualVolumeMgr.expandVirtualVolume(virtualVolumeName, expansionStatusRetryCount,
expansionStatusSleepTime);
}
/**
* For the virtual volume with the passed name, migrates the data on the
* backend volume(s) to the backend volumes identified by the passed native
* volume information.
*
* @param migrationName The name for this migration.
* @param virtualVolumeName The name of the virtual volume whose data is to
* be migrated.
* @param nativeVolumeInfoList The native information for the volume(s) to
* which the data should be migrated.
* @param isRemote true if the the migration is across clusters, else false.
* @param useDeviceMigration true if device migration is required.
* @param discoveryRequired true if the passed native volumes are newly
* exported and need to be discovered by the VPlex.
* @param startNow true to start the migration now, else migration is
* created in a paused state.
* @param transferSize The transfer size for migration
* @return A reference to the migration(s) started to migrate the virtual
* volume.
*
* @throws VPlexApiException When an error occurs creating and/or
* initializing the migration.
*/
public List<VPlexMigrationInfo> migrateVirtualVolume(String migrationName,
String virtualVolumeName, List<VolumeInfo> nativeVolumeInfoList,
boolean isRemote, boolean useDeviceMigration, boolean discoveryRequired,
boolean startNow, String transferSize) throws VPlexApiException {
s_logger.info("Request for virtual volume migration on VPlex at {}", _baseURI);
return _migrationMgr.migrateVirtualVolume(migrationName, virtualVolumeName,
nativeVolumeInfoList, isRemote, useDeviceMigration, discoveryRequired, startNow, transferSize);
}
/**
* Returns the information, including the current status, of the migration
* with the passed name.
*
* @param migrationName The name of the migration.
*
* @return A VPlex migration info reference.
*
* @throws VPlexApiException When an error occurs getting the latest
* information for the migration or the migration is not found.
*/
public VPlexMigrationInfo getMigrationInfo(String migrationName) throws VPlexApiException {
List<VPlexMigrationInfo> migrationInfoList = _discoveryMgr
.findMigrations(Arrays.asList(migrationName));
return migrationInfoList.get(0);
}
/**
* Pauses the executing migrations with the passed name.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs pausing the migrations.
*/
public void pauseMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Request to pause migrations on VPlex at {}", _baseURI);
_migrationMgr.pauseMigrations(migrationNames);
}
/**
* Resume the paused migrations with with the passed names.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs resuming the migrations.
*/
public void resumeMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Request to resume migrations on VPlex at {}", _baseURI);
_migrationMgr.resumeMigrations(migrationNames);
}
/**
* Commits the completed migrations with the passed names and tears down the
* old devices and unclaims the storage volumes.
*
* @param virtualVolumeName The name of the virtual volume prior to the commit.
* @param migrationNames The names of the migrations.
* @param cleanup true to automatically cleanup after the commit.
* @param remove true to automatically remove the migration record.
* @param rename true to rename the volumes after committing the migration.
*
* @return A list of VPlexMigrationInfo instances for the committed
* migrations each of which contains a reference to the
* VPlexVirtualVolumeInfo associated with that migration which can
* be used to update the virtual volume native id, which can change
* as a result of the migration.
*
* @throws VPlexApiException When an error occurs committing the migrations.
*/
public List<VPlexMigrationInfo> commitMigrations(String virtualVolumeName, List<String> migrationNames,
boolean cleanup, boolean remove, boolean rename) throws VPlexApiException {
s_logger.info("Request to commit migrations on VPlex at {}", _baseURI);
return _migrationMgr.commitMigrations(virtualVolumeName, migrationNames, cleanup, remove, rename);
}
/**
* Cleans the committed migrations with the passed names tearing down the
* old devices and unclaiming the storage volumes.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs cleaning the migrations.
*/
public void cleanMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Request to clean migrations on VPlex at {}", _baseURI);
_migrationMgr.cleanMigrations(migrationNames);
}
/**
* Cancels the uncommitted, executing, paused, or queued migrations with the
* passed names and tears down the new devices that were created as targets
* for the migration and unclaims the storage volumes.
*
* @param migrationNames The names of the migrations.
* @param cleanup true to automatically cleanup after the cancellation.
* @param remove true to automatically remove the migration record.
*
* @throws VPlexApiException When an error occurs canceling the migrations.
*/
public void cancelMigrations(List<String> migrationNames, boolean cleanup,
boolean remove) throws VPlexApiException {
s_logger.info("Request to cancel migrations on VPlex at {}", _baseURI);
_migrationMgr.cancelMigrations(migrationNames, cleanup, remove);
}
/**
* Removes the records for the committed or canceled migrations with the
* passed names.
*
* @param migrationNames The names of the migrations.
*
* @throws VPlexApiException When an error occurs removing the migration
* records.
*/
public void removeMigrations(List<String> migrationNames) throws VPlexApiException {
s_logger.info("Request to remove migrations on VPlex at {}", _baseURI);
_migrationMgr.removeMigrations(migrationNames);
}
/**
* 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) {
s_logger.info(String.format("Request to set virtual volume %s to thin-enabled at %s", virtualVolumeInfo.getName(), _baseURI));
return _virtualVolumeMgr.setVirtualVolumeThinEnabled(virtualVolumeInfo);
}
/**
* Take the list of initiators to check and register any that cannot be found
* already registered on the VPLEX.
*
* @param initiatorsToCheck the initiators to check for registration
* @param vplexClusterName the VPLEX cluster name (like cluster-1 or cluster-2)
* @throws VPlexApiException if something went wrong
*/
public void registerInitiators(List<PortInfo> initiatorsToCheck, String vplexClusterName) throws VPlexApiException {
s_logger.info("Request for registering initiators in cluster {} on VPLEX at {}", vplexClusterName, _baseURI);
VPlexClusterInfo clusterInfo = getClusterInfoLiteForClusterName(vplexClusterName);
List<VPlexInitiatorInfo> initiatorsFound =
_exportMgr.findInitiators(vplexClusterName, initiatorsToCheck);
if (initiatorsFound.size() != initiatorsToCheck.size()) {
s_logger.info("Could not find all of the requested initiators on VPLEX.");
// get a list of all initiators and register them (the register method checks the name for status)
initiatorsFound = _exportMgr.buildInitiatorInfoList(initiatorsFound, initiatorsToCheck, clusterInfo);
_exportMgr.registerInitiators(clusterInfo, initiatorsFound);
// clear the cache so that we'll get a fresh reading next request
_discoveryMgr.clearInitiatorCache(vplexClusterName);
} else {
s_logger.info("All the requested initiators have already been registered.");
}
}
/**
* Creates a VPlex storage view so that the passed initiators have access to
* the passed virtual volumes, via the passed target ports (i.e., the VPlex
* front-end ports). Note that target ports are required to create a storage
* view. Initiator ports and virtual volumes are optional and can be added
* separately.
*
* @param viewName A unique name for the storage view.
* @param targetPortInfo The info for the target ports.
* @param initiatorPortInfo The info for the initiator ports.
* @param virtualVolumeMap Map of virtual volume names to LUN ID.
*
* NOTE: If you want VPlex to pick the LUN ID pass
* VPlexApiConstants.LUN_UNASSIGNED for the virtual volume.
*
* @return A reference to a VPlexStorageViewInfo specifying the storage view
* information.
*
* @throws VPlexApiException When an error occurs creating the storage view.
*/
public VPlexStorageViewInfo createStorageView(String viewName,
List<PortInfo> targetPortInfo, List<PortInfo> initiatorPortInfo,
Map<String, Integer> virtualVolumeMap) throws VPlexApiException {
s_logger.info("Request for storage view creation on VPlex at {}", _baseURI);
// A storage view name is required. It must be unique across all
// clusters of the VPlex. We could do a check here, but it would
// require an additional request, or perhaps 2 in a Metro/Geo
// configuration.
if ((viewName == null) || (viewName.trim().length() == 0)) {
throw new VPlexApiException(
"A name for the storage view must be specified.");
}
// Targets are required to create a storage view.
if (targetPortInfo.isEmpty()) {
throw new VPlexApiException(
"Target ports are required to create a storage view");
}
return _exportMgr.createStorageView(viewName, targetPortInfo, initiatorPortInfo,
virtualVolumeMap);
}
/**
* Delete the storage view with the passed name and existence
* tracking parameter.
*
* @param viewName The name of the storage view to be deleted.
* @param clusterName The name of the VPLEX cluster that the storage view is on.
* @param viewFound An out parameter indicating whether or
* not the storage view was actually found on
* the VPLEX device during this process.
*
* @throws VPlexApiException When an error occurs deleting the storage view.
*/
public void deleteStorageView(String viewName, String clusterName, Boolean[] viewFound) throws VPlexApiException {
s_logger.info("Request for storage view deletion on VPlex at {}", _baseURI);
_exportMgr.deleteStorageView(viewName, clusterName, viewFound);
s_logger.info("Storage view was found for deletion: {}", viewFound[0]);
}
/**
* Adds the initiators identified by the passed port information to the
* storage view with the passed name.
*
* @param viewName The name of the storage view.
* @param clusterName The name of the VPLEX cluster that the storage view is on.
* @param initiatorPortInfo The port information for the initiators to be
* added.
*
* @throws VPlexApiException When an error occurs adding the initiators.
*/
public void addInitiatorsToStorageView(String viewName, String clusterName,
List<PortInfo> initiatorPortInfo) throws VPlexApiException {
s_logger.info("Request to add initiators to storage view on VPlex at {}",
_baseURI);
_exportMgr.addInitiatorsToStorageView(viewName, clusterName, initiatorPortInfo);
}
/**
* Removes the initiators identified by the passed port information from the
* storage view with the passed name.
*
* @param viewName The name of the storage view.
* @param clusterName The name of the VPLEX cluster that the storage view is on.
* @param initiatorPortInfo The port information for the initiators to be
* removed.
*
* @throws VPlexApiException When an error occurs removing the initiators.
*/
public void removeInitiatorsFromStorageView(String viewName, String clusterName,
List<PortInfo> initiatorPortInfo) throws VPlexApiException {
s_logger.info("Request to remove initiators from storage view on VPlex at {}",
_baseURI);
_exportMgr.removeInitiatorsFromStorageView(viewName, clusterName, initiatorPortInfo);
}
/**
* Add targets to the storage view identified by name.
*
* @param viewName -- StorageView name
* @param targetPortInfo -- The port information for the targets to be added.
* @throws VPlexApiException When an error occurs adding the targets
*/
public void addTargetsToStorageView(String viewName,
List<PortInfo> targetPortInfo) throws VPlexApiException {
s_logger.info("Request to add targets to storage view on VPlex at {}", _baseURI);
_exportMgr.addTargetsToStorageView(viewName, targetPortInfo);
}
/**
* Remove targets from the storage view identified by name.
*
* @param viewName -- Storage view name
* @param targetPortInfo -- The port information for the targets to be removed.
* @throws VPlexApiException
*/
public void removeTargetsFromStorageView(String viewName,
List<PortInfo> targetPortInfo) throws VPlexApiException {
s_logger.info("Request to remove targets to storage view on VPlex at {}", _baseURI);
_exportMgr.removeTargetsFromStorageView(viewName, targetPortInfo);
}
/**
* Adds the virtual volumes with the passed names to the storage view with
* the passed name.
*
* @param viewName The name of the storage view.
* @param clusterName The name of the VPLEX cluster that the storage view is on.
* @param virtualVolumeMap Map of virtual volume names to LUN ID.
*
* NOTE: If you want VPlex to pick the LUN ID pass
* VPlexApiConstants.LUN_UNASSIGNED for the virtual volume.
*
* @return A reference to a VPlexStorageViewInfo specifying the storage view
* information.
*
* @throws VPlexApiException When an error occurs adding the virtual
* volumes.
*/
public VPlexStorageViewInfo addVirtualVolumesToStorageView(String viewName, String clusterName,
Map<String, Integer> virtualVolumeMap) throws VPlexApiException {
s_logger.info("Request to add virtual volumes to storage view on VPlex at {}",
_baseURI);
return _exportMgr.addVirtualVolumesToStorageView(viewName, clusterName, virtualVolumeMap);
}
/**
* Removes the virtual volumes with the passed names from the storage view
* with the passed name.
*
* @param viewName The name of the storage view.
* @param clusterName The name of the VPLEX cluster that the storage view is on.
* @param virtualVolumeNames The names of the virtual volumes to be removed.
*
* @throws VPlexApiException When an error occurs removing the virtual
* volumes.
*/
public void removeVirtualVolumesFromStorageView(String viewName, String clusterName,
List<String> virtualVolumeNames) throws VPlexApiException {
s_logger.info(
"Request to remove virtual volumes from storage view on VPlex at {}",
_baseURI);
_exportMgr.removeVirtualVolumesFromStorageView(viewName, clusterName, virtualVolumeNames);
}
/**
* Gets the name of the cluster on which the passed storage volumes resides.
*
* @param volumeInfo The native volume info for the volumes.
*
* @return The name of the cluster on which the volume resides.
*
* @throws VPlexApiException If an error occurs finding the volume.
*/
public String getClaimedStorageVolumeClusterName(VolumeInfo volumeInfo)
throws VPlexApiException {
VPlexStorageVolumeInfo storageVolumeInfo = _discoveryMgr
.findStorageVolume(volumeInfo.getVolumeName());
return storageVolumeInfo.getClusterId();
}
/**
* Gets all consistency groups on the VPLEX.
*
* @return A list of VPlexConsistencyGroupInfo instances corresponding to
* the consistency groups on the VPLEX.
*
* @throws VPlexApiException When an error occurs get the consistency
* groups.
*/
public List<VPlexConsistencyGroupInfo> getConsistencyGroups()
throws VPlexApiException {
s_logger.info("Request to get all consistency groups on VPlex at {}", _baseURI);
return _discoveryMgr.getConsistencyGroups();
}
/**
* Creates a consistency group with the passed name on the cluster with the
* passed name.
*
* @param cgName The name for the consistency group.
* @param clusterName The name of the cluster on which the group is created.
* @param isDistributed true if the CG will hold distributed volumes.
*
* @throws VPlexApiException When an error occurs creating the consistency
* group.
*/
public void createConsistencyGroup(String cgName, String clusterName,
boolean isDistributed) throws VPlexApiException {
s_logger.info("Request to create consistency group on VPlex at {}", _baseURI);
_cgMgr.createConsistencyGroup(cgName, clusterName, isDistributed);
}
/**
* Updates the consistency group properties. Should only be invoked
* when the consistency group has no volumes.
*
* @param cgName The name for the consistency group.
* @param clusterName The name of the cluster for which the CG should be
* found, when not distributed.
* @param isDistributed true if the CG will hold distributed volumes.
*
* @throws VPlexApiException When an error occurs setting the consistency
* group properties.
*/
public void updateConsistencyGroupProperties(String cgName, String clusterName,
boolean isDistributed) throws VPlexApiException {
s_logger.info("Request to update consistency group properties on VPlex at {}",
_baseURI);
List<VPlexClusterInfo> clusterInfoList = _discoveryMgr.getClusterInfoLite();
if (!isDistributed) {
Iterator<VPlexClusterInfo> clusterInfoIter = clusterInfoList.iterator();
while (clusterInfoIter.hasNext()) {
VPlexClusterInfo clusterInfo = clusterInfoIter.next();
if (!clusterInfo.getName().equals(clusterName)) {
clusterInfoIter.remove();
}
}
// Find the consistency group.
VPlexConsistencyGroupInfo cgInfo = _discoveryMgr.findConsistencyGroup(cgName,
clusterInfoList, true);
// The changes we need to make depend on if the the consistency group
// has visibility to both clusters indicating it previously held
// distributed volumes.
List<String> visibleClusters = cgInfo.getVisibility();
if (visibleClusters.size() > 1) {
// Must set to no automatic winner as the CG may have previously held
// distributed volumes that had a winner set, which would cause a failure
// when we set update the visibility and cluster info.
_cgMgr.setDetachRuleNoAutomaticWinner(cgInfo);
// Now we must clear the CG storage clusters.
_cgMgr.setConsistencyGroupStorageClusters(cgInfo, new ArrayList<VPlexClusterInfo>());
// Now set visibility and cluster info for the CG in this order.
_cgMgr.setConsistencyGroupVisibility(cgInfo, clusterInfoList);
_cgMgr.setConsistencyGroupStorageClusters(cgInfo, clusterInfoList);
} else if (!visibleClusters.contains(clusterName)) {
// Update the visibility and cluster info for the CG in this order.
_cgMgr.setConsistencyGroupStorageClusters(cgInfo, new ArrayList<VPlexClusterInfo>());
_cgMgr.setConsistencyGroupVisibility(cgInfo, clusterInfoList);
_cgMgr.setConsistencyGroupStorageClusters(cgInfo, clusterInfoList);
}
} else {
// Find the consistency group.
VPlexConsistencyGroupInfo cgInfo = _discoveryMgr.findConsistencyGroup(cgName,
clusterInfoList, true);
// We only have to make changes if the visibility is not
// currently both clusters.
List<String> visibleClusters = cgInfo.getVisibility();
if (visibleClusters.size() != 2) {
// First clear the CG storage clusters.
_cgMgr.setConsistencyGroupStorageClusters(cgInfo, new ArrayList<VPlexClusterInfo>());
// Now set visibility and cluster info for the CG to both clusters
// in this order.
_cgMgr.setConsistencyGroupVisibility(cgInfo, clusterInfoList);
_cgMgr.setConsistencyGroupStorageClusters(cgInfo, clusterInfoList);
// Now set the detach rule to winner for the cluster with the passed name.
VPlexClusterInfo winningCluster = null;
Iterator<VPlexClusterInfo> clusterInfoIter = clusterInfoList.iterator();
while (clusterInfoIter.hasNext()) {
VPlexClusterInfo clusterInfo = clusterInfoIter.next();
if (clusterInfo.getName().equals(clusterName)) {
winningCluster = clusterInfo;
break;
}
}
_cgMgr.setDetachRuleWinner(cgInfo, winningCluster);
}
}
}
/**
* Updates the consistency group RP enabled tag.
*
* @param cgName The name for the consistency group.
* @param clusterName The name of the cluster for which the CG should be
* visible, when not distributed.
* @param isRPEnabled true if the CG will hold RP protected VPLEX volumes.
*
* @throws VPlexApiException When an error occurs setting the consistency
* group visibility.
*/
public void updateConsistencyRPEnabled(String cgName, String clusterName,
boolean isRPEnabled) throws VPlexApiException {
s_logger.info("Request to update consistency group RP enabled on VPlex at {}",
_baseURI);
List<VPlexClusterInfo> clusterInfoList = _discoveryMgr.getClusterInfoLite();
Iterator<VPlexClusterInfo> clusterInfoIter = clusterInfoList.iterator();
while (clusterInfoIter.hasNext()) {
VPlexClusterInfo clusterInfo = clusterInfoIter.next();
if (!clusterInfo.getName().equals(clusterName)) {
clusterInfoIter.remove();
}
}
_cgMgr.setConsistencyGroupRPEnabled(cgName, clusterInfoList, isRPEnabled);
}
/**
* Adds the volumes with the passed names to the consistency group with the
* passed name.
*
* @param cgName The name of the consistency group to which the volumes are
* added.
* @param virtualVolumeNames The names of the virtual volumes to be added to
* the consistency group.
*
* @throws VPlexApiException When an error occurs adding the volumes to the
* consistency group.
*/
public void addVolumesToConsistencyGroup(String cgName,
List<String> virtualVolumeNames) throws VPlexApiException {
s_logger.info("Request to add volumes to a consistency group on VPlex at {}",
_baseURI);
_cgMgr.addVolumesToConsistencyGroup(cgName, virtualVolumeNames);
}
/**
* Removes the volumes with the passed names from the consistency group with
* the passed name. If the removal of the volumes results in an empty group,
* delete the consistency group if the passed flag so indicates.
*
* @param virtualVolumeNames The names of the virtual volumes to be removed
* from the consistency group.
* @param cgName The name of the consistency group from which the volume is
* removed.
* @param deleteCGWhenEmpty true to delete the consistency group if the
* group is empty after removing the volumes, false otherwise.
*
* @return true if the consistency group was deleted, false otherwise.
*
* @throws VPlexApiException When an error occurs removing the volumes from
* the consistency group.
*/
public boolean removeVolumesFromConsistencyGroup(List<String> virtualVolumeNames,
String cgName, boolean deleteCGWhenEmpty) throws VPlexApiException {
s_logger
.info("Request to remove volumes from a consistency group on VPlex at {}",
_baseURI);
return _cgMgr.removeVolumesFromConsistencyGroup(virtualVolumeNames, cgName,
deleteCGWhenEmpty);
}
/**
* Deletes the consistency group with the passed name.
*
* @param cgName The name of the consistency group to be deleted.
*
* @throws VPlexApiException When an error occurs deleting the consistency group.
*/
public void deleteConsistencyGroup(String cgName) throws VPlexApiException {
s_logger.info("Request to delete consistency group on VPlex at {}",
_baseURI);
_cgMgr.deleteConsistencyGroup(cgName);
}
/**
* 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 value in the response "message"
* 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 on VPLEX at {}",
_baseURI);
return _virtualVolumeMgr.invalidateVirtualVolumeCache(virtualVolumeName);
}
/**
* 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 cache invalidate status information on VPLEX at {}",
_baseURI);
return _virtualVolumeMgr.getCacheStatus(virtualVolumeName);
}
/**
* Will attempt to dismantle the local device by destroying all components used
* to build up the device (i.e, extents).
*
* @param deviceInfo The information specifying the device backend volume.
*
* @throws VPlexApiException When an error occurs detaching the mirror from the volume.
*/
public void deleteLocalDevice(VolumeInfo deviceInfo) throws VPlexApiException {
s_logger.info("Request to delete local VPLex device at {}",
_baseURI);
_virtualVolumeMgr.deleteLocalDevice(deviceInfo);
}
/**
* Will attempt to dismantle the local device by destroying all components used
* to build up the device (i.e, extents).
*
* @param localDeviceName The name of the local device.
*
* @throws VPlexApiException When an error occurs detaching the mirror from the volume.
*/
public void deleteLocalDevice(String localDeviceName) throws VPlexApiException {
s_logger.info("Request to delete local VPLex device at {}",
_baseURI);
_virtualVolumeMgr.deleteLocalDevice(localDeviceName);
}
/**
* Detaches the mirror specified by the passed mirror info from the
* VPLEX volume with the passed name.
*
* @param virtualVolumeName The name of the VPLEX volume.
* @param mirrorDeviceName The name of the mirror device
* @param discard The boolean to use 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 a mirror from a local virtual volume at {}",
_baseURI);
_virtualVolumeMgr.detachMirrorFromLocalVirtualVolume(virtualVolumeName, mirrorDeviceName, discard);
}
/**
* Detaches the mirror specified by the passed mirror info from the
* VPLEX volume with the passed name.
*
* @param virtualVolumeName The name of the VPLEX volume.
* @param mirrorDeviceName The name of the mirror device
* @param discard The boolean to use 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 a mirror from a local virtual volume at {}",
_baseURI);
_virtualVolumeMgr.detachLocalMirrorFromDistributedVirtualVolume(virtualVolumeName, mirrorDeviceName, discard);
}
/**
* 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 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 a mirror from a distributed volume at {}",
_baseURI);
return _virtualVolumeMgr.detachMirrorFromDistributedVolume(virtualVolumeName, clusterId);
}
/**
* Renames the distributed device with the passed name to the passed new name.
*
* @param currentName The current device name
* @param newName The new device name.
*/
public void renameDistributedDevice(String currentName, String newName) {
VPlexDistributedDeviceInfo ddInfo = _discoveryMgr.findDistributedDevice(currentName);
if (ddInfo != null) {
renameResource(ddInfo, newName);
} else {
s_logger.error("Can't find distributed device {} for rename request.", currentName);
throw VPlexApiException.exceptions.cantFindDistributedDeviceForRename(currentName);
}
}
/**
* 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 on VPLEX at {}",
_baseURI);
_virtualVolumeMgr.reattachMirrorToDistributedVolume(virtualVolumeName, detachedDeviceName);
}
public VPlexVirtualVolumeInfo upgradeVirtualVolumeToDistributed(VPlexVirtualVolumeInfo virtualVolume,
VolumeInfo newRemoteVolume, boolean discoveryRequired, String clusterId, String transferSize) throws VPlexApiException {
return _virtualVolumeMgr.createDistributedVirtualVolume(
virtualVolume, newRemoteVolume, discoveryRequired, clusterId, transferSize);
}
public WaitOnRebuildResult waitOnRebuildCompletion(String virtualVolume)
throws VPlexApiException {
return _virtualVolumeMgr.waitOnRebuildCompletion(virtualVolume);
}
/**
* Returns a map of the port WWNs of all initiators to the initiator name in
* the VPlex.
*
* @param clusterName indicates which VPlex cluster to perform the operation on
* @return a map of port WWNs to initiator names. Note the keys (WWNs) have
* no colons.
*/
private synchronized Map<String, String> getInitiatorWwnToNameMap(String clusterName) {
if (!_vplexClusterInitiatorWwnToNameCache.containsKey(clusterName) ||
_vplexClusterInitiatorWwnToNameCache.get(clusterName) == null ||
_vplexClusterInitiatorWwnToNameCache.get(clusterName).isEmpty()) {
long start = System.currentTimeMillis();
s_logger.info("refreshing initiator wwn-to-name cache for cluster " + clusterName);
Map<String, String> clusterInitiatorToNameMap = _discoveryMgr.getInitiatorWwnToNameMap(clusterName, false);
s_logger.info("TIMER: refreshing initiator wwn-to-name cache took {}ms", System.currentTimeMillis() - start);
_vplexClusterInitiatorWwnToNameCache.put(clusterName, clusterInitiatorToNameMap);
}
return _vplexClusterInitiatorWwnToNameCache.get(clusterName);
}
/**
* Gets the VPLEX initiator name for an upper case wwn with no colons,
* or null if not found.
*
* @param vplexClusterName the name of the VPLEX cluster to check
* @param wwn the wwn to find an Initiator name for
* @param doRefresh trigger a cache refresh, once done, this out param will be set to false
* so that subsequent calls don't have to refresh the cache
* @return the name of the Initiator
*/
public synchronized String getInitiatorNameForWwn(String vplexClusterName, String wwn, Boolean[] doRefresh) {
String initiatorName = null;
if (!getInitiatorWwnToNameMap(vplexClusterName).containsKey(wwn)) {
s_logger.info(
"initiator wwn to name cache does not contain an entry for wwn {} on vplex cluster {}",
wwn, vplexClusterName);
if (doRefresh[0]) {
s_logger.info("clearing vplex cluster {} cache for refresh", vplexClusterName);
_vplexClusterInitiatorWwnToNameCache.get(vplexClusterName).clear();
_discoveryMgr.clearInitiatorCache(vplexClusterName);
doRefresh[0] = false;
} else {
return null;
}
}
initiatorName = getInitiatorWwnToNameMap(vplexClusterName).get(wwn);
s_logger.info("initiator name {} found for wwn {}", initiatorName, wwn);
return initiatorName;
}
/**
* Returns a list of VPlexStorageViewInfo objects representing
* storage views that contain the given initiator names.
*
* @param clusterName the VPLEX cluster to look in
* @param initiatorNames the initiator names to look for
* @return a list of VPlexStorageViewInfo objects
*/
public List<VPlexStorageViewInfo> getStorageViewsContainingInitiators(
String clusterName, List<String> initiatorNames) {
return _discoveryMgr.getStorageViewsContainingInitiators(clusterName,
initiatorNames);
}
/**
* Finds the cluster name for a given cluster id.
*
* @param clusterId the cluster id (1 or 2)
* @return the name for the cluster or null in none found
*/
public String getClusterNameForId(String clusterId) {
String clusterName = getClusterIdToNameMap().get(clusterId);
if (clusterName != null && !clusterName.isEmpty()) {
s_logger.info("found cluster name {} for cluster id {}", clusterName, clusterId);
return clusterName;
}
s_logger.error("VPLEX cluster name for cluster id {} not found.", clusterId);
throw VPlexApiException.exceptions.couldNotFindCluster(clusterId);
}
/**
* Gets a map of cluster IDs to cluster names for the VPLEX device.
*
* @return a map of cluster IDs to cluster names for the VPLEX device
*/
public synchronized Map<String, String> getClusterIdToNameMap() {
if (_vplexClusterIdToNameCache.isEmpty()) {
List<VPlexClusterInfo> clusterInfos = getClusterInfoLite();
for (VPlexClusterInfo clusterInfo : clusterInfos) {
_vplexClusterIdToNameCache.put(clusterInfo.getClusterId(), clusterInfo.getName());
}
s_logger.info("refreshed cluster id to name map is " + _vplexClusterIdToNameCache.toString());
}
// return a copy
return new HashMap<String, String>(_vplexClusterIdToNameCache);
}
/**
* Package protected getter for the base URI for the client.
*
* @return The base URI for the client.
*/
URI getBaseURI() {
return _baseURI;
}
/**
* Package protected getter for the API client discovery manager.
*
* @return The API client discovery manager.
*/
VPlexApiDiscoveryManager getDiscoveryManager() {
return _discoveryMgr;
}
/**
* Package protected getter for the API client virtual volume manager.
*
* @return The API client virtual volume manager.
*/
VPlexApiVirtualVolumeManager getVirtualVolumeManager() {
return _virtualVolumeMgr;
}
/**
* Package protected getter for the API client export manager.
*
* @return The API client export manager.
*/
VPlexApiExportManager getExportManager() {
return _exportMgr;
}
/**
* Execute a GET request with the default JSON format=0 and cache
* control max age value of 0 seconds.
*
* @param resourceURI The resource URI.
* @return The client response.
*/
ClientResponse get(URI resourceURI) {
return get(resourceURI,
VPlexApiConstants.ACCEPT_JSON_FORMAT_0,
VPlexApiConstants.CACHE_CONTROL_MAXAGE_ZERO);
}
/**
* Execute a GET request with the cache control max age value of
* 0 seconds and JSON format set by the caller.
*
* @param resourceURI The resource URI.
* @param jsonFormat The expected JSON response format.
* See VPlexApiConstants.ACCEPT_JSON_FORMAT_*
* @return The client response.
*/
ClientResponse get(URI resourceURI, String jsonFormat) {
return get(resourceURI, jsonFormat,
VPlexApiConstants.CACHE_CONTROL_MAXAGE_ZERO);
}
/**
* Package protected method for executing a GET request.
*
* @param resourceURI The resource URI.
* @param jsonFormat The expected JSON response format.
* See VPlexApiConstants.ACCEPT_JSON_FORMAT_*
* @param cacheControlMaxAge cache control max age
*
* @return The client response.
*/
ClientResponse get(URI resourceURI, String jsonFormat, String cacheControlMaxAge) {
int retryCount = 0;
ClientResponse response = null;
while (++retryCount <= GET_RETRY_COUNT) {
try {
response = _client.get(resourceURI, _vplexSessionId,
jsonFormat, cacheControlMaxAge);
updateVPLEXSessionId(response);
break;
} catch (Exception e) {
s_logger.warn("VPLEX API client GET request {} failed. Retrying...", resourceURI, e);
if (retryCount < GET_RETRY_COUNT) {
VPlexApiUtils.pauseThread(GET_SLEEP_TIME_MS);
} else {
throw e;
}
}
}
return response;
}
/**
* Execute a POST request with the default JSON format=0.
*
* @param resourceURI The resource URI.
* @param postData The POST data.
* @return The client response.
*/
ClientResponse post(URI resourceUri, String postData) {
return post(resourceUri, postData, VPlexApiConstants.ACCEPT_JSON_FORMAT_0);
}
/**
* Package protected method for executing a POST request.
*
* @param resourceURI The resource URI.
* @param postData The POST data.
*
* @return The client response.
*/
ClientResponse post(URI resourceURI, String postData, String jsonFormat) {
ClientResponse response = _client.post(resourceURI, postData, _vplexSessionId, jsonFormat);
updateVPLEXSessionId(response);
return response;
}
/**
* Execute a PUT request with the default JSON format=0.
*
* @param resourceURI The resource URI.
* @return The client response.
*/
ClientResponse put(URI resourceURI) {
return put(resourceURI, VPlexApiConstants.ACCEPT_JSON_FORMAT_0);
}
/**
* Package protected method for executing a PUT request.
*
* @param resourceURI The resource URI.
* @param jsonFormat The expected JSON response format.
* See VPlexApiConstants.ACCEPT_JSON_FORMAT_*
*
* @return The client response.
*/
ClientResponse put(URI resourceURI, String jsonFormat) {
ClientResponse response = _client.put(resourceURI, _vplexSessionId, jsonFormat);
updateVPLEXSessionId(response);
return response;
}
static private int maxAsyncPollingRetries = VPlexApiConstants.MAX_RETRIES;
static private int maxMigrationAsyncPollingRetries = VPlexApiConstants.MAX_RETRIES;
/**
* Polls for asynchronous completion using the default maximum retries.
*
* @see #waitForCompletion(ClientResponse, int)
* @param asyncResponse
* @return
*/
String waitForCompletion(ClientResponse asyncResponse) {
return waitForCompletion(asyncResponse, maxAsyncPollingRetries);
}
/**
* When a VPlex command is executed via a POST request, if that command
* takes longer than 60 seconds to complete, the VPlex command will return a
* response with 202 status. The Location header of that response will point
* to a URI which specifies a task that can be monitored. This function gets
* the task resource from the Location header of the passed asynchronous
* response and will wait for the task to complete successfully, fail, or
* timeout.
*
* @param asyncResponse A response from the VPlex indicating a command is being run
* asynchronously because it is taking too long.
* @param maxRetries -- Maximum polling attempts before timeout.
*
* @throws VPlexApiException When the command completes unsuccessfully or we
* time out because it is taking too long.
*/
String waitForCompletion(ClientResponse asyncResponse, int maxRetries) throws VPlexApiException {
MultivaluedMap<String, String> headers = asyncResponse.getHeaders();
String taskResourceStr = headers.getFirst(VPlexApiConstants.LOCATION_HEADER);
if (taskResourceStr == null) {
throw new VPlexApiException("Can't find location for asynchronous reponse.");
}
s_logger.info("Waiting for task {} to complete", taskResourceStr);
// Check the task for completion until we've reached the
// maximum number of status checks.
int retries = 0;
while (retries++ < maxRetries) {
ClientResponse taskResponse = get(URI.create(taskResourceStr));
String responseStr = taskResponse.getEntity(String.class);
s_logger.info("Wait for completion response is {}", responseStr);
int taskStatus = taskResponse.getStatus();
taskResponse.close();
if (taskStatus == VPlexApiConstants.SUCCESS_STATUS) {
// Task completed successfully
s_logger.info("Task {} completed successfully", taskResourceStr);
return responseStr;
} else if (taskStatus != VPlexApiConstants.TASK_PENDING_STATUS) {
// Task failed.
throw new VPlexApiException(String.format(
"Task %s did not complete successfully", taskResourceStr));
} else {
// Task is still pending completion, sleep a bit and check again.
VPlexApiUtils.pauseThread(VPlexApiConstants.TASK_PENDING_WAIT_TIME);
}
}
// We've timed out waiting for the operation to complete.
throw VPlexApiException.exceptions
.timeoutWaitingForAsyncOperationToComplete(taskResourceStr);
}
/**
* Gets the target info for the passed ports.
*
* @param portInfoList The list of ports.
*
* @return A map of the associated target info for the ports keyed by the
* port WWN.
*
* @throws VPlexApiException When an error occurs getting the target info.
*/
public Map<String, VPlexTargetInfo> getTargetInfoForPorts(
List<VPlexPortInfo> portInfoList) throws VPlexApiException {
s_logger.info("Request to get target info for ports at {}", _baseURI);
return _exportMgr.getTargetInfoForPorts(portInfoList);
}
/**
* Returns a VPlexVirtualVolumeInfo object for the given virtual volume name
* and context path. An attempt will first be made to get the virtual volume
* directly by its path, but if that fails, this method will do the old "search
* the entire VPLEX" process. If still no volume is found, it will return null.
*
* @param virtualVolumeName the virtual volume name
* @param virtualVolumePath the virtual volume path
* @return a VPlexVirtualVolumeInfo object or null if none is found
*/
public VPlexVirtualVolumeInfo findVirtualVolume(String virtualVolumeName, String virtualVolumePath) {
VPlexVirtualVolumeInfo vvinfo = null;
if (virtualVolumePath != null && !virtualVolumePath.isEmpty()) {
// first attempt to get by the much more efficient native id
try {
vvinfo = getVirtualVolumeByPath(virtualVolumePath);
} catch (Exception ex) {
s_logger.warn("Didn't find virtual volume by path at {}, will check by name {}",
virtualVolumePath, virtualVolumeName);
}
}
if (null == vvinfo && (null != virtualVolumeName && !virtualVolumeName.isEmpty())) {
// otherwise, fall back on the "search the whole vplex" method
vvinfo = findVirtualVolumeAndUpdateInfo(virtualVolumeName);
}
s_logger.info("returning virtual volume: ");
return vvinfo;
}
/**
* This method gets a virtual volume on the VPLEX directly by full context path.
*
* @param virtualVolumePath the virtual volume context path
* @return VPlexVirtualVolumeInfo object with full details
*/
private VPlexVirtualVolumeInfo getVirtualVolumeByPath(String virtualVolumePath) {
return _discoveryMgr.getVirtualVolumeByPath(virtualVolumePath);
}
/**
* Finds virtual volume by name.
*
* @param clusterInfoList List of detailed VPLEX cluster info.
* @param virtualVolumeInfos List of virtual volumes to find
* @return A map of virtual volume name to the virtual volume info.
*/
public Map<String, VPlexVirtualVolumeInfo> findVirtualVolumes(List<VPlexClusterInfo> clusterInfoList,
List<VPlexVirtualVolumeInfo> virtualVolumeInfos) {
return _discoveryMgr.findVirtualVolumes(clusterInfoList, virtualVolumeInfos, true, true);
}
/**
* Updates the VPLEX session id with the session specified in the
* passed response.
*
* @param response A response resulting from a request to the VPLEX.
*/
private void updateVPLEXSessionId(ClientResponse response) {
List<NewCookie> cookies = response.getCookies();
for (NewCookie cookie : cookies) {
// Look for the session cookie. It should returned in the
// response if this is the first request from this client
// or the session expired since the last request, in which
// case the VPLEX creates a new session and returns the
// id in the cookie.
if (VPlexApiConstants.SESSION_COOKIE.equals(cookie.getName())) {
String newSessionId = cookie.getValue();
if ((_vplexSessionId == null) || (!_vplexSessionId.equals(newSessionId))) {
s_logger.info("VPLEX Session ID changing from {} to {}",
(_vplexSessionId == null ? "null" : _vplexSessionId),
newSessionId);
_vplexSessionId = newSessionId;
}
break;
}
}
}
/**
* Returns a Map of lowest-level storage-volume resource's WWN to its VPlexStorageVolumeInfo
* object for a given device name, virtual volume type, and cluster name. If
* hasMirror is true, this indicates the top-level device is composed of a
* RAID-1 mirror, so there's an extra layers of components to traverse in finding
* the lowest-level storage-volume resources.
*
* @param deviceName the name of the top-level device to look at
* @param virtualVolumeType the type of virtual volume (local or distributed)
* @param clusterName the cluster name
* @param hasMirror indicates if the top-level device is a RAID-1 mirror
*
* @return a map of WWNs to VPlexStorageVolumeInfo objects
* @throws VPlexApiException
*/
public Map<String, VPlexStorageVolumeInfo> getStorageVolumeInfoForDevice(
String deviceName, String virtualVolumeType,
String clusterName, boolean hasMirror) throws VPlexApiException {
s_logger.info("Request to find storage volume wwns for {} on VPLEX at {}",
deviceName, _baseURI);
List<VPlexStorageVolumeInfo> storageVolumes = getDiscoveryManager()
.getStorageVolumesForDevice(deviceName, virtualVolumeType, clusterName, hasMirror);
if (!storageVolumes.isEmpty()) {
s_logger.info("storage volumes found:");
Iterator<VPlexStorageVolumeInfo> it = storageVolumes.iterator();
while (it.hasNext()) {
VPlexStorageVolumeInfo info = it.next();
s_logger.info(info.toString());
if (!VPlexApiConstants.STORAGE_VOLUME_TYPE.equals(info.getComponentType())) {
s_logger.warn("Unexpected component type {} found for volume {}",
info.getComponentType(), info.getName());
it.remove();
}
}
}
Map<String, VPlexStorageVolumeInfo> storageVolumeWwns = new HashMap<String, VPlexStorageVolumeInfo>();
for (VPlexStorageVolumeInfo info : storageVolumes) {
if (null == info.getWwn()) {
String reason = "could not parse WWN for storage volume " + info.getName();
s_logger.error(reason);
throw VPlexApiException.exceptions.failedGettingStorageVolumeInfoForIngestion(reason);
}
String wwn = info.getWwn();
if (wwn != null && !wwn.isEmpty()) {
wwn = wwn.replaceAll("[^A-Fa-f0-9]", "");
wwn = wwn.toUpperCase();
}
storageVolumeWwns.put(wwn, info);
}
return storageVolumeWwns;
}
/**
* Returns the top-level supporting device name for a given storage with the passed
* native storage volume information.
*
* @param vInfo The native storage volume information.
*
* @return the name of the top level device for the given storage volume
* @throws VPlexApiException
*/
@Deprecated
public String getDeviceForStorageVolume(VolumeInfo vInfo) throws VPlexApiException {
s_logger.info("Request to find device name for storage volume {} on VPLEX at {}",
vInfo.getVolumeNativeId(), _baseURI);
String deviceName = getDiscoveryManager().getDeviceForStorageVolume(vInfo);
return deviceName;
}
/**
* Returns a VPlexResourceInfo object for the given device name based
* on its virtual volume type (local or distributed).
*
* @param deviceName the name of the device
* @param virtualVolumeType the type of virtual volume (local or distributed)
*
* @return a VPlexResourceInfo object for the device name
* @throws VPlexApiException
*/
public VPlexResourceInfo getDeviceStructure(String deviceName, String virtualVolumeType)
throws VPlexApiException {
s_logger.info("Request to find {} device structure for {} on VPLEX at " + _baseURI,
virtualVolumeType, deviceName);
VPlexResourceInfo device = null;
switch (virtualVolumeType) {
case VPlexApiConstants.DISTRIBUTED_VIRTUAL_VOLUME:
device = getDiscoveryManager()
.getDeviceStructureForDistributedIngestion(deviceName);
break;
case VPlexApiConstants.LOCAL_VIRTUAL_VOLUME:
device = getDiscoveryManager()
.getDeviceStructureForLocalIngestion(deviceName);
break;
}
return device;
}
/**
* Returns a Map of distributed device component context
* paths from the VPLEX API to VPLEX cluster names.
*
* @return a Map of distributed device component context
* paths from the VPLEX API to VPLEX cluster names
*
* @throws VPlexApiException
*/
public Map<String, String> getDistributedDevicePathToClusterMap()
throws VPlexApiException {
return _discoveryMgr.getDistributedDevicePathToClusterMap();
}
/**
* This method finds virtual volume on the VPLEX and then updates virtual volume info.
*
* @param virtualVolumeName virtual volume name
* @return VPlexVirtualVolumeInfo object with updated info.
*/
protected VPlexVirtualVolumeInfo findVirtualVolumeAndUpdateInfo(String virtualVolumeName) {
return _virtualVolumeMgr.findVirtualVolumeAndUpdateInfo(virtualVolumeName, null);
}
/**
* This method collapses the one legged device for the passed virtual volume device.
*
* @param sourceDeviceNameOrPath source device name or path
* @param collapseType "local" or "distributed" or "collapse-by-path"
* @throws VPlexApiException
*/
public void deviceCollapse(String sourceDeviceNameOrPath, String collapseType) throws VPlexApiException {
s_logger.info("Request to collapse device {} with collapse type {}", sourceDeviceNameOrPath, collapseType);
_virtualVolumeMgr.deviceCollapse(sourceDeviceNameOrPath, collapseType);
}
/**
* This method sets device visibility to local.
*
* @param sourceDeviceName source device name
* @throws VPlexApiException
*/
public void setDeviceVisibility(String sourceDeviceName) throws VPlexApiException {
s_logger.info("Request to set device visibility {}", _baseURI);
_virtualVolumeMgr.setDeviceVisibility(sourceDeviceName);
}
/**
* Calls the VPLEX CLI "drill-down" command for the given device name.
*
* @param deviceName a device name to check with the drill-down command
* @return the String drill-down command response from the VPLEX API
* @throws VPlexApiException if the device structure is incompatible with ViPR
*/
public String getDrillDownInfoForDevice(String deviceName) throws VPlexApiException {
return _discoveryMgr.getDrillDownInfoForDevice(deviceName);
}
/**
* Returns the maximum number of retries for a polling operation.
*
* @return integer number of retries
*/
static public int getMaxAsyncPollingRetries() {
return maxAsyncPollingRetries;
}
/**
* Sets the maximum number of retries for an async. polling operation.
*
* @param maxRetries integer
*/
static public void setMaxAsyncPollingRetries(int maxRetries) {
maxAsyncPollingRetries = maxRetries;
}
/**
* Returns the maximum number of async. retries for a migration commit.
*
* @return integer number of retries
*/
static public int getMaxMigrationAsyncPollingRetries() {
return maxMigrationAsyncPollingRetries;
}
/**
* Sets the maximum number of async. retries for a migration commit.
*
* @param maxRetries integer
*/
static public void setMaxMigrationAsyncPollingRetries(int maxRetries) {
maxMigrationAsyncPollingRetries = maxRetries;
}
/**
* Validates that the backend volumes represented by the passed native volume info
* from the ViPR database are the actual backend volumes used by the passed VPLEX volume.
*
* @param virtualVolumeName The name of the VPLEX volume.
* @param virtualVolumePath The path to the VPLEX volume.
* @param nativeVolumeInfoMap The native volume info for expected backend volumes keyed by cluster name.
*
* @throws VPlexApiException When an exception occurs validating the backend volumes.
*/
public void validateBackendVolumesForVPlexVolume(String virtualVolumeName, String virtualVolumePath,
Map<String, List<VolumeInfo>> nativeVolumeInfoMap)
throws VPlexApiException {
s_logger.info("Validating backend volumes for VPLEX volume {}", virtualVolumeName);
// Find the VPLEX volume.
VPlexVirtualVolumeInfo vvInfo = findVirtualVolume(virtualVolumeName, virtualVolumePath);
if (vvInfo == null) {
s_logger.error("Could not find VPLEX volume {} to validate its backend volumes", virtualVolumeName);
throw VPlexApiException.exceptions.couldNotFindVolumeForValidation(virtualVolumeName);
}
// Get and validate the name of the supporting device.
String supportingDeviceName = vvInfo.getSupportingDevice();
if ((supportingDeviceName == null) || (supportingDeviceName.isEmpty())) {
s_logger.error("VPLEX volume {} does not specify a supporting device", virtualVolumeName);
throw VPlexApiException.exceptions.noSupportingDeviceForValidation(virtualVolumeName);
}
// Validate the passed storage volume info map.
String locality = vvInfo.getLocality();
Set<String> clusterNames = nativeVolumeInfoMap.keySet();
if (((VPlexVirtualVolumeInfo.Locality.distributed.name().equals(locality)) && (clusterNames.size() != 2)) ||
((VPlexVirtualVolumeInfo.Locality.local.name().equals(locality)) && (clusterNames.size() != 1))) {
s_logger.error("Invalid native volume information passed for validation of VPLEX volume {}", virtualVolumeName);
throw VPlexApiException.exceptions.invalidVolumeInfoForValidation(virtualVolumeName, locality);
}
// Get the cluster information, which will get the storage volume
// information on both clusters.
List<VPlexClusterInfo> clusterInfoList = getClusterInfoDetails();
// Validate the expected backend storage volumes on each cluster.
Iterator<String> clusterNameIter = clusterNames.iterator();
while (clusterNameIter.hasNext()) {
String clusterName = clusterNameIter.next();
s_logger.info("Validating backend volumes on cluster {}", clusterName);
// Find the backend storage volumes on the cluster using the passed
// volume info for that cluster. These will be the backend volumes
// that ViPR believes are the backend volumes used by the passed
// virtual volume.
List<VolumeInfo> nativeVolumeInfoList = nativeVolumeInfoMap.get(clusterName);
List<VPlexStorageVolumeInfo> expectedStorageVolumeInfoList = new ArrayList<>();
for (VPlexClusterInfo clusterInfo : clusterInfoList) {
if (clusterInfo.getName().equals(clusterName)) {
for (VolumeInfo nativeVolumeInfo : nativeVolumeInfoList) {
VPlexStorageVolumeInfo expectedStorageVolumeInfo = clusterInfo.getStorageVolume(nativeVolumeInfo);
if (expectedStorageVolumeInfo != null) {
expectedStorageVolumeInfoList.add(expectedStorageVolumeInfo);
}
}
}
}
// Validate we found these volumes.
if (expectedStorageVolumeInfoList.size() != nativeVolumeInfoList.size()) {
s_logger.error("Did not find all expected backend volumes for VPLEX volume {}", virtualVolumeName);
throw VPlexApiException.exceptions.failFindingExpectedBackendVolumesForValidation(virtualVolumeName,
nativeVolumeInfoList.size(), expectedStorageVolumeInfoList.size());
}
// Get the actual backend storage volumes used by the supporting device of
// the virtual volume on the cluster. If we are looking for 2 volumes on
// a cluster, the volume has a mirror on that cluster.
boolean hasMirror = (nativeVolumeInfoList.size() == 2);
List<VPlexStorageVolumeInfo> actualStorageVolumeInfoList = _discoveryMgr.getBackendVolumesForDeviceOnCluster(
supportingDeviceName, locality, clusterName, hasMirror);
// The actual and expected storage volumes should have the same names.
for (VPlexStorageVolumeInfo expectedStorageVolumeInfo : expectedStorageVolumeInfoList) {
boolean volumeMatch = false;
String expectedStorageVolumeName = expectedStorageVolumeInfo.getName();
for (VPlexStorageVolumeInfo actualStorageVolumeInfo : actualStorageVolumeInfoList) {
String actualStorageVolumeName = actualStorageVolumeInfo.getName();
if (expectedStorageVolumeName.equalsIgnoreCase(actualStorageVolumeName)) {
s_logger.info("Validated backend volume {}", expectedStorageVolumeName);
volumeMatch = true;
break;
}
}
if (!volumeMatch) {
s_logger.error("Failed to validate storage volume {}", expectedStorageVolumeName);
throw VPlexApiException.exceptions.storageVolumeFailedValidation(virtualVolumeName,
expectedStorageVolumeName);
}
}
}
}
/**
* Updates the read-only flag in a ConsistencyGroup.
* @param cgName -- Consistency group name
* @param clusterName - Cluster name for CG
* @param isDistributed - True if the CG is a distributed CG in both clusters
* @param isReadOnly -- Set up read-only
*/
public void updateConsistencyGroupReadOnly(String cgName, String clusterName, boolean isDistributed,
boolean isReadOnly) {
s_logger.info("Request to update consistency group read-only on VPlex at {}",
_baseURI);
List<VPlexClusterInfo> clusterInfoList = _discoveryMgr.getClusterInfoLite();
Iterator<VPlexClusterInfo> clusterInfoIter = clusterInfoList.iterator();
if (!isDistributed) {
while (clusterInfoIter.hasNext()) {
VPlexClusterInfo clusterInfo = clusterInfoIter.next();
if (!clusterInfo.getName().equals(clusterName)) {
clusterInfoIter.remove();
}
}
}
_cgMgr.setConsistencyGroupReadOnly(cgName, clusterInfoList, isReadOnly);
}
/**
* Gets information for the target FE ports on the cluster with the passed
* name.
*
* @param clusterName The name of the cluster.
*
* @return A list of VPlexTargetInfo instances specifying the target
* information.
*
* @throws VPlexApiException When an error occurs getting the target
* information for the cluster.
*/
public List<VPlexTargetInfo> getTargetInfoForCluster(String clusterName)
throws VPlexApiException {
s_logger.info("Request to get target port info for cluster {}", clusterName);
return getDiscoveryManager().getTargetInfoForCluster(clusterName);
}
/**
* Gets all the detailed Storage View infos for the give VPLEX cluster.
*
* @param clusterName name of the VPLEX cluster to look at, or you can send
* a wildcard (*) to get info from both clusters.
* @return list of all Storage View infos for a given VPLEX instance
* @throws VPlexApiException
*/
public List<VPlexStorageViewInfo> getStorageViewsForCluster(String clusterName) throws VPlexApiException {
s_logger.info("Request to get storage view info for cluster {}", clusterName);
return getDiscoveryManager().getStorageViewsForCluster(clusterName, true);
}
/**
* Clears the local VPLEX REST API info caches.
*/
public synchronized void clearCaches() {
_vplexClusterIdToNameCache.clear();
_vplexClusterInfoLiteCache.clear();
_vplexClusterInitiatorWwnToNameCache.clear();
_discoveryMgr.clearInitiatorCache();
}
/**
* Clears the initiator cache for the given cluster.
*
* @param vplexClusterName the cluster initiator cache to clear
*/
public synchronized void clearInitiatorCache(String vplexClusterName) {
_discoveryMgr.clearInitiatorCache(vplexClusterName);
}
/**
* Primes the local VPLEX REST API info caches.
*/
public synchronized void primeCaches() {
// prime the cluster id to name map, then use the values to prime the initiator to wwn map
for (String clusterName : getClusterIdToNameMap().values()) {
_discoveryMgr.getInitiatorWwnToNameMap(clusterName, true);
}
}
}