package com.emc.storageos.vplexcontroller;
import java.net.URI;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume.SupportedVolumeCharacterstics;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume.SupportedVolumeInformation;
import com.emc.storageos.vplex.api.VPlexApiConstants;
import com.emc.storageos.vplex.api.VPlexApiException;
import com.emc.storageos.vplex.api.VPlexDeviceInfo;
import com.emc.storageos.vplex.api.VPlexDistributedDeviceInfo;
import com.emc.storageos.vplex.api.VPlexResourceInfo;
import com.emc.storageos.vplex.api.VPlexStorageVolumeInfo;
/**
* A context object for holding all the data and functionality
* required to discover and ingest a VPLEX virtual volume's
* backend volumes and related replicas.
*/
public class VplexBackendIngestionContext {
protected static Logger _logger = LoggerFactory.getLogger(VplexBackendIngestionContext.class);
public static final String VOLUME = "VOLUME";
public static final String UNMANAGEDVOLUME = "UNMANAGEDVOLUME";
public static final String INGESTION_METHOD_FULL = "Full";
public static final String INGESTION_METHOD_VVOL_ONLY = "VirtualVolumesOnly";
public static final String DISCOVERY_MODE = "controller_vplex_volume_discovery_mode";
public static final String DISCOVERY_MODE_DISCOVERY_ONLY = "Only During Discovery";
public static final String DISCOVERY_MODE_INGESTION_ONLY = "Only During Ingestion";
public static final String DISCOVERY_MODE_HYBRID = "During Discovery and Ingestion";
public static final String DISCOVERY_MODE_DB_ONLY = "Only Use Database";
public static final String DISCOVERY_FILTER = "controller_vplex_volume_discovery_filter";
public static final String DISCOVERY_KILL_SWITCH = "controller_vplex_volume_discovery_kill_switch";
public static final String SLOT_0 = "0";
public static final String SLOT_1 = "1";
public static final String VVOL_LABEL1 = "dd_";
public static final String VVOL_LABEL2 = "device_";
protected final DbClient _dbClient;
private final UnManagedVolume _unmanagedVirtualVolume;
private final String _originalVolumeLabel;
private boolean _discoveryInProgress = false;
private boolean _ingestionInProgress = false;
private boolean _inDiscoveryOnlyMode = false;
private boolean _shouldCheckForMirrors = false;
private VPlexResourceInfo topLevelDevice;
private List<UnManagedVolume> unmanagedBackendVolumes;
private List<UnManagedVolume> unmanagedSnapshots;
private Map<UnManagedVolume, Set<UnManagedVolume>> unmanagedVplexClones;
private Map<UnManagedVolume, Set<UnManagedVolume>> unmanagedBackendOnlyClones;
private Map<UnManagedVolume, String> unmanagedMirrors;
private Map<String, Map<String, VPlexDeviceInfo>> mirrorMap;
private Map<String, VPlexStorageVolumeInfo> backendVolumeWwnToInfoMap;
private Map<String, String> distributedDevicePathToClusterMap;
private Project backendProject;
private Project frontendProject;
// A map of BlockSnapshot instances that are created during VPLEX backend ingestion. Snapshots
// can be created when the VPLEX backend volume is also a snapshot target volume.
private final Map<String, BlockSnapshot> createdSnapshotsMap = new HashMap<String, BlockSnapshot>();
private final BackendDiscoveryPerformanceTracker _tracker;
/**
* Constructor taking the virtual volume's UnManagedVolume object
* and a reference to the database client. This constructor will
* also create an internal instance of the BackendDiscoveryPerformanceTracker.
*
* @param unManagedVolume the parent UnManagedVolume for the virtual volume
* @param dbClient a reference to the database client
*/
public VplexBackendIngestionContext(UnManagedVolume unManagedVolume, DbClient dbClient) {
this._unmanagedVirtualVolume = unManagedVolume;
this._dbClient = dbClient;
this._tracker = new BackendDiscoveryPerformanceTracker();
this._originalVolumeLabel = unManagedVolume.getLabel();
}
/**
* Calls any methods which would require contacting the VPLEX API
* for further information during the VPLEX UnManagedVolume discovery process.
*/
public void discover() {
this.setDiscoveryInProgress(true);
this.getUnmanagedBackendVolumes();
this.getUnmanagedVplexMirrors();
this.getUnmanagedVplexClones();
this.getUnmanagedBackendOnlyClones();
}
/**
* Gets the parent virtual volume's UnManagedVolume object.
*
* @return the parent virtual volume's UnManagedVolume object
*/
public UnManagedVolume getUnmanagedVirtualVolume() {
return this._unmanagedVirtualVolume;
}
/**
* Collects and returns all the various backend resource UnManagedVolume objects -
* includes those for the backend volumes and any related VPLEX-native replicas.
*
* @return a List of all the UnManagedVolumes to ingest for this context
*/
public List<UnManagedVolume> getUnmanagedVolumesToIngest() {
List<UnManagedVolume> volumesToIngest = new ArrayList<UnManagedVolume>();
volumesToIngest.addAll(getUnmanagedBackendVolumes());
volumesToIngest.addAll(getUnmanagedVplexMirrors().keySet());
_logger.info("unmanaged volumes to ingest: " + volumesToIngest);
return volumesToIngest;
}
/**
* Gets URIs for the backend UnManagedVolume objects.
*
* @return a List of URIs for the backend UnManagedVolume objects
*/
public List<URI> getUnmanagedBackendVolumeUris() {
List<URI> allUris = new ArrayList<URI>();
for (UnManagedVolume vol : getUnmanagedBackendVolumes()) {
allUris.add(vol.getId());
}
return allUris;
}
/**
* Returns a List of all the backend associated UnManagedVolume objects (i.e.,
* the actual associated volumes, not replicas).
*
* @return a List of the backend associated UnManagedVolume objects
*/
public List<UnManagedVolume> getUnmanagedBackendVolumes() {
if (null != unmanagedBackendVolumes) {
return unmanagedBackendVolumes;
}
if (!isDiscoveryInProgress()) {
// first check the database for this unmanaged volume's backend volumes
unmanagedBackendVolumes = findBackendUnManagedVolumes(_unmanagedVirtualVolume, _dbClient);
if (null != unmanagedBackendVolumes) {
return unmanagedBackendVolumes;
}
}
// if they couldn't be found in the database,
// we will query the VPLEX API for this information
long start = System.currentTimeMillis();
_logger.info("getting unmanaged backend volumes");
unmanagedBackendVolumes = new ArrayList<UnManagedVolume>();
// if we're in discovery only mode, don't check again during ingestion
if (isIngestionInProgress() && isInDiscoveryOnlyMode()) {
return unmanagedBackendVolumes;
}
List<URI> associatedVolumeUris = new ArrayList<URI>();
if (!getBackendVolumeWwnToInfoMap().isEmpty()) {
for (String backendWwn : getBackendVolumeWwnToInfoMap().keySet()) {
_logger.info("attempting to find unmanaged backend volume by wwn {}",
backendWwn);
URIQueryResultList results = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.
Factory.getUnmanagedVolumeWwnConstraint(
BlockObject.normalizeWWN(backendWwn)), results);
if (results.iterator() != null) {
for (URI uri : results) {
_logger.info("found backend volume " + uri);
associatedVolumeUris.add(uri);
}
}
}
}
unmanagedBackendVolumes = _dbClient.queryObject(UnManagedVolume.class, associatedVolumeUris);
if (null == unmanagedBackendVolumes || unmanagedBackendVolumes.isEmpty()) {
_logger.warn("no backend volumes were found for {}, have the "
+ "backend storage arrays already been discovered?",
_unmanagedVirtualVolume.getLabel());
} else {
_logger.info("for VPLEX UnManagedVolume {}, found these associated volumes: "
+ unmanagedBackendVolumes, _unmanagedVirtualVolume.getLabel());
}
updateUnmanagedBackendVolumesInParent();
_tracker.fetchBackendVolumes = System.currentTimeMillis() - start;
return unmanagedBackendVolumes;
}
/**
* Sets the VPLEX_BACKEND_VOLUMES information on the virtual UnManagedVolume
* as well as the VPLEX_PARENT_VOLUME and VPLEX_BACKEND_CLUSTER_ID
* on each associated UnManagedVolume.
*/
private void updateUnmanagedBackendVolumesInParent() {
if (!getUnmanagedBackendVolumes().isEmpty()) {
StringSet bvols = new StringSet();
for (UnManagedVolume backendVol : unmanagedBackendVolumes) {
bvols.add(backendVol.getNativeGuid());
// set the parent volume native guid on the backend volume
StringSet parentVol = new StringSet();
parentVol.add(_unmanagedVirtualVolume.getNativeGuid());
backendVol.putVolumeInfo(SupportedVolumeInformation.VPLEX_PARENT_VOLUME.name(), parentVol);
if (isDistributed()) {
// determine cluster location of distributed component storage volume leg
VPlexStorageVolumeInfo storageVolume =
getBackendVolumeWwnToInfoMap().get(backendVol.getWwn());
if (null != storageVolume) {
String clusterId = getClusterLocationForStorageVolume(storageVolume);
if (null != clusterId && !clusterId.isEmpty()) {
_logger.info("setting VPLEX_BACKEND_CLUSTER_ID: " + clusterId);
StringSet clusterIds = new StringSet();
clusterIds.add(clusterId);
backendVol.putVolumeInfo(
SupportedVolumeInformation.VPLEX_BACKEND_CLUSTER_ID.name(),
clusterIds);
}
}
}
_dbClient.updateObject(backendVol);
}
if (bvols != null && !bvols.isEmpty()) {
_logger.info("setting VPLEX_BACKEND_VOLUMES: " + unmanagedBackendVolumes);
_unmanagedVirtualVolume.putVolumeInfo(SupportedVolumeInformation.VPLEX_BACKEND_VOLUMES.name(), bvols);
_unmanagedVirtualVolume.setLabel(getFriendlyLabel());
}
}
}
/**
* Returns a friendly label for the virtual volume based on the names of
* the backend volumes. This should better reflect what the user originally
* created as a label for the volume, rather than the VPLEX format (device_ or dd_).
*
* This is somewhat a "best effort" and will fall back on the default VPLEX label
* format if a better name can't be determined safely.
*
* @return a friendly label for the virtual volume
*/
private String getFriendlyLabel() {
String friendlyLabel = _originalVolumeLabel;
boolean hasBackendMirror = unmanagedMirrors != null && !unmanagedMirrors.isEmpty();
if (_unmanagedVirtualVolume.getLabel() == null || _unmanagedVirtualVolume.getLabel().startsWith(VVOL_LABEL1) ||
_unmanagedVirtualVolume.getLabel().startsWith(VVOL_LABEL2) || hasBackendMirror) {
if (null != unmanagedBackendVolumes && !unmanagedBackendVolumes.isEmpty()) {
if (unmanagedBackendVolumes.size() > VPlexApiConstants.LOCAL_BACKEND_VOLUME_COUNT) {
// sort so that we consistently pick the same backend volume for the label
Comparator<UnManagedVolume> sorter = new Comparator<UnManagedVolume>() {
public int compare(UnManagedVolume o1, UnManagedVolume o2) {
return o1.getLabel().compareTo(o2.getLabel());
};
};
unmanagedBackendVolumes.sort(sorter);
}
UnManagedVolume backendVol = unmanagedBackendVolumes.get(0);
if (null != backendVol) {
String baseLabel = backendVol.getLabel();
// Remove the -0 or -1 from the backing volume label, if it's there.
if (baseLabel.endsWith("-0") || baseLabel.endsWith("-1")) {
baseLabel = baseLabel.substring(0, baseLabel.length() - 2);
}
if (null != _originalVolumeLabel && !_originalVolumeLabel.isEmpty()) {
// put the existing virtual volume label inside parentheses for reference
friendlyLabel = baseLabel + " (" + _originalVolumeLabel + ")";
} else {
friendlyLabel = baseLabel;
}
}
}
}
_logger.info("Determined friendly label to be {} for UnManagedVolume {}",
friendlyLabel, _unmanagedVirtualVolume.forDisplay());
return friendlyLabel;
}
/**
* Queries the VPLEX API to get a Map of backend storage volume WWNs
* to VPlexStorageVolumeInfo objects.
*
* @return a Map of backend storage volume WWNs to VPlexStorageVolumeInfo objects
*/
private Map<String, VPlexStorageVolumeInfo> getBackendVolumeWwnToInfoMap() {
if (null != backendVolumeWwnToInfoMap) {
return backendVolumeWwnToInfoMap;
}
_logger.info("getting backend volume wwn to api info map");
// first trying without checking for a top-level device mirror to save some time
backendVolumeWwnToInfoMap =
VPlexControllerUtils.getStorageVolumeInfoForDevice(
getSupportingDeviceName(), getLocality(), getClusterName(), false,
getVplexUri(), _dbClient);
_logger.info("found these wwns: " + backendVolumeWwnToInfoMap.keySet());
boolean notEnoughWwnsFound =
(isLocal() && backendVolumeWwnToInfoMap.isEmpty()) ||
(isDistributed() && backendVolumeWwnToInfoMap.size() < 2);
if (notEnoughWwnsFound) {
_logger.info("not enough volume wwns were found, search deeper in the component tree");
// try again and check for mirrors first
boolean hasMirror = !getMirrorMap().isEmpty();
_shouldCheckForMirrors = true;
if (hasMirror) {
// the volume has a mirrored top-level device, so we need to
// send the hasMirror flag down so that the VPLEX client will
// know to look one level deeper in the components tree for
// the backend storage volumes
Map<String, VPlexStorageVolumeInfo> deeperBackendVolumeWwnToInfoMap =
VPlexControllerUtils.getStorageVolumeInfoForDevice(
getSupportingDeviceName(), getLocality(), getClusterName(), hasMirror,
getVplexUri(), _dbClient);
_logger.info("went deeper and found these wwns: " + deeperBackendVolumeWwnToInfoMap.keySet());
for (Entry<String, VPlexStorageVolumeInfo> entry : deeperBackendVolumeWwnToInfoMap.entrySet()) {
backendVolumeWwnToInfoMap.put(entry.getKey(), entry.getValue());
}
}
}
notEnoughWwnsFound =
(isLocal() && backendVolumeWwnToInfoMap.isEmpty()) ||
(isDistributed() && backendVolumeWwnToInfoMap.size() < 2);
if (notEnoughWwnsFound) {
String reason = "could not find enough backend storage volume wwns for "
+ getSupportingDeviceName()
+ ", but did find these: " + backendVolumeWwnToInfoMap.keySet();
_logger.error(reason);
throw VPlexApiException.exceptions.backendIngestionContextLoadFailure(reason);
}
_logger.info("backend volume wwn to api info map: " + backendVolumeWwnToInfoMap);
return backendVolumeWwnToInfoMap;
}
/**
* Gets a List of all the backend volume native GUIDs as
* they would appear in a Volume object (not as in an
* UnManagedVolume object).
*
* @return a List of all the backend volume native GUIDs
*/
public List<String> getBackendVolumeGuids() {
List<String> associatedVolumeGuids = new ArrayList<String>();
for (UnManagedVolume vol : this.getUnmanagedBackendVolumes()) {
associatedVolumeGuids.add(vol.getNativeGuid().replace(UNMANAGEDVOLUME, VOLUME));
}
_logger.info("associated volume guids: " + associatedVolumeGuids);
return associatedVolumeGuids;
}
/**
* Determines if a given UnManagedVolume is one of the backend
* associated volumes of this context.
*
* @param volumeToCheck the UnManagedVolume to check
* @return true if the volume is a backend volume
*/
public boolean isBackendVolume(UnManagedVolume volumeToCheck) {
String id = volumeToCheck.getId().toString();
for (UnManagedVolume vol : getUnmanagedBackendVolumes()) {
if (id.equals(vol.getId().toString())) {
return true;
}
}
return false;
}
/**
* Returns a List of any block snapshots associated
* with the backend volumes of this context's virtual volume.
*
* @return a List of UnManagedVolume snaphot objects
*/
public List<UnManagedVolume> getUnmanagedSnapshots() {
if (null != unmanagedSnapshots) {
return unmanagedSnapshots;
}
long start = System.currentTimeMillis();
_logger.info("getting unmanaged snapshots");
unmanagedSnapshots = new ArrayList<UnManagedVolume>();
for (UnManagedVolume sourceVolume : this.getUnmanagedBackendVolumes()) {
unmanagedSnapshots.addAll(getUnManagedSnaphots(sourceVolume));
}
_logger.info("found these associated snapshots: " + unmanagedSnapshots);
_tracker.fetchSnapshots = System.currentTimeMillis() - start;
return unmanagedSnapshots;
}
/**
* Returns a Map of parent backend volume to child backend volumes
* for any clones (full copies) associated with the backend volumes
* of this context's virtual volume.
*
* The term "backend-only clone" implies that the clone is only a copy
* of the backend volume and there is no virtual volume in front of it.
* This is as-opposed to a "full clone" that has a virtual volume in
* front of it.
*
* @return a Map of UnManagedVolume parent objects to UnManagedVolume child objects
*/
public Map<UnManagedVolume, Set<UnManagedVolume>> getUnmanagedBackendOnlyClones() {
if (null != unmanagedBackendOnlyClones) {
return unmanagedBackendOnlyClones;
}
long start = System.currentTimeMillis();
_logger.info("getting unmanaged backend-only clones");
unmanagedBackendOnlyClones = new HashMap<UnManagedVolume, Set<UnManagedVolume>>();
for (UnManagedVolume backendVolume : getUnmanagedBackendVolumes()) {
List<UnManagedVolume> clonesForThisVolume = getUnManagedClones(backendVolume);
if (clonesForThisVolume != null) {
for (UnManagedVolume clone : clonesForThisVolume) {
String parentVvol = extractValueFromStringSet(
SupportedVolumeInformation.VPLEX_PARENT_VOLUME.name(),
clone.getVolumeInformation());
if (parentVvol == null || parentVvol.isEmpty()) {
if (!unmanagedBackendOnlyClones.containsKey(backendVolume)) {
Set<UnManagedVolume> cloneSet = new HashSet<UnManagedVolume>();
unmanagedBackendOnlyClones.put(backendVolume, cloneSet);
}
_logger.info("could not find a parent virtual volume for backend clone {}",
clone.getLabel());
unmanagedBackendOnlyClones.get(backendVolume).add(clone);
}
}
}
}
_logger.info("unmanaged backend-only clones found: " + unmanagedBackendOnlyClones);
_tracker.fetchBackendOnlyClones = System.currentTimeMillis() - start;
return unmanagedBackendOnlyClones;
}
/**
* Returns a Map of clone backend volume to front-end virtual volume clone
* for any clones (full copies) associated with this context's virtual volume.
*
* The term "vplex clone" implies that the clone is a backend volume clone with
* a front-end virtual volume containing it. This is as-opposed to a backend-only
* clone, which is just a backend array clone of a backend volume without a virtual
* volume in front of it.
*
* @return a Map of UnManagedVolume backend objects to UnManagedVolume front-end objects
*/
public Map<UnManagedVolume, Set<UnManagedVolume>> getUnmanagedVplexClones() {
if (null != unmanagedVplexClones) {
return unmanagedVplexClones;
}
long start = System.currentTimeMillis();
_logger.info("getting unmanaged full virtual volume clones");
unmanagedVplexClones = new HashMap<UnManagedVolume, Set<UnManagedVolume>>();
for (UnManagedVolume backendVolume : getUnmanagedBackendVolumes()) {
List<UnManagedVolume> clonesForThisVolume = getUnManagedClones(backendVolume);
if (clonesForThisVolume != null) {
for (UnManagedVolume clone : clonesForThisVolume) {
if (!unmanagedVplexClones.containsKey(backendVolume)) {
Set<UnManagedVolume> cloneSet = new HashSet<UnManagedVolume>();
unmanagedVplexClones.put(backendVolume, cloneSet);
}
String parentVvol = extractValueFromStringSet(
SupportedVolumeInformation.VPLEX_PARENT_VOLUME.name(),
clone.getVolumeInformation());
if (parentVvol != null && !parentVvol.isEmpty()) {
_logger.info("found parent virtual volume {} for backend clone {}",
parentVvol, clone.getLabel());
unmanagedVplexClones.get(backendVolume).add(clone);
}
}
}
}
_logger.info("unmanaged full virtual volume clones found: " + unmanagedVplexClones);
_tracker.fetchVplexClones = System.currentTimeMillis() - start;
return unmanagedVplexClones;
}
/**
* Returns a Map of UnManagedVolume objects that are parts
* of a VplexMirror to their device context path from the
* VPLEX API.
*
* @return a map of UnManagedVolume to device context paths
*/
public Map<UnManagedVolume, String> getUnmanagedVplexMirrors() {
if (null != unmanagedMirrors) {
return unmanagedMirrors;
}
if (!isDiscoveryInProgress()) {
// first check the database for this unmanaged volume's backend mirrors
StringSet mirrorMapFromTheDatabase = extractValuesFromStringSet(
SupportedVolumeInformation.VPLEX_MIRROR_MAP.toString(),
_unmanagedVirtualVolume.getVolumeInformation());
if (null != mirrorMapFromTheDatabase && !mirrorMapFromTheDatabase.isEmpty()) {
_logger.info("fetching mirror map from database");
for (String mirrorEntry : mirrorMapFromTheDatabase) {
// extract 'n' parse the mirror info from the database
// pair[0] is the native id of the mirror
// pair[1] is the device context path from the VPLEX API
String[] pair = mirrorEntry.split("=");
UnManagedVolume mirrorVolume = null;
String contextPath = pair[1];
// find the mirror UnManagedVolume object
URIQueryResultList unManagedVolumeList = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getVolumeInfoNativeIdConstraint(pair[0]), unManagedVolumeList);
if (unManagedVolumeList.iterator().hasNext()) {
mirrorVolume = _dbClient.queryObject(UnManagedVolume.class,
unManagedVolumeList.iterator().next());
}
// add to the map that will be returned from this method
if (null != mirrorVolume && null != contextPath) {
if (null == unmanagedMirrors) {
unmanagedMirrors = new HashMap<UnManagedVolume, String>();
}
unmanagedMirrors.put(mirrorVolume, contextPath);
// now remove the mirror from the list of regular backend volumes
// so that it won't be ingested that way
Iterator<UnManagedVolume> itr = getUnmanagedBackendVolumes().iterator();
while (itr.hasNext()) {
if (mirrorVolume.getId().toString().equals(itr.next().getId().toString())) {
itr.remove();
}
}
}
}
if (null != unmanagedMirrors && !unmanagedMirrors.isEmpty()) {
_logger.info("found mirror map: " + unmanagedMirrors);
return unmanagedMirrors;
}
}
}
unmanagedMirrors = new HashMap<UnManagedVolume, String>();
// if a simple 1-1-1 device structure was found, no need
// to check for native mirrors
if (!_shouldCheckForMirrors) {
return unmanagedMirrors;
}
// if we're in discovery only mode, don't check again during ingestion
if (isIngestionInProgress() && isInDiscoveryOnlyMode()) {
return unmanagedMirrors;
}
// if the mirror map couldn't be found in the database,
// we will query the VPLEX API for this information
long start = System.currentTimeMillis();
_logger.info("getting unmanaged mirrors");
if (!getMirrorMap().isEmpty()) {
//
// the mirrorMap is structured like: Map<ClusterName, Map<SlotNumber, VPlexDeviceInfo>>
//
for (Entry<String, Map<String, VPlexDeviceInfo>> mirrorMapEntry : getMirrorMap().entrySet()) {
_logger.info("looking at mirrors for device leg on cluster " + mirrorMapEntry.getKey());
Map<String, VPlexDeviceInfo> slotToDeviceMap = mirrorMapEntry.getValue();
if (null != slotToDeviceMap && !slotToDeviceMap.isEmpty()) {
// figure out the source and target (mirror) UnManagedVolumes for this leg
UnManagedVolume associatedVolumeSource = null;
UnManagedVolume associatedVolumeMirror = null;
// source will be in slot-0, target/mirror will be in slot-1
for (Entry<String, VPlexDeviceInfo> entry : slotToDeviceMap.entrySet()) {
if (SLOT_0.equals(entry.getKey())) {
_logger.info("looking at slot-0");
associatedVolumeSource = getAssociatedVolumeForComponentDevice(entry.getValue());
}
if (SLOT_1.equals(entry.getKey())) {
_logger.info("looking at slot-1");
associatedVolumeMirror = getAssociatedVolumeForComponentDevice(entry.getValue());
}
}
// once found, wire them together:
if (null != associatedVolumeMirror && null != associatedVolumeSource) {
// 1. remove the mirror volume from the general backend volumes
_logger.info("removing mirror volume {} from associated "
+ "vols and adding to mirrors", associatedVolumeMirror.getLabel());
getUnmanagedBackendVolumes().remove(associatedVolumeMirror);
// 2. add the mirror the unmanagedMirrors map that will be returned by this method
unmanagedMirrors.put(associatedVolumeMirror, slotToDeviceMap.get("1").getPath());
// 3. update the source volume with the target mirror information
StringSet set = new StringSet();
set.add(associatedVolumeMirror.getNativeGuid());
_logger.info("adding mirror set {} to source unmanaged volume {}",
set, associatedVolumeSource);
associatedVolumeSource.putVolumeInfo(
SupportedVolumeInformation.VPLEX_NATIVE_MIRROR_TARGET_VOLUME.toString(), set);
_logger.info("setting VPLEX_BACKEND_CLUSTER_ID on mirrored volumes: "
+ mirrorMapEntry.getKey());
StringSet clusterIds = new StringSet();
clusterIds.add(mirrorMapEntry.getKey());
associatedVolumeSource.putVolumeInfo(
SupportedVolumeInformation.VPLEX_BACKEND_CLUSTER_ID.name(),
clusterIds);
// 4. update the target volume with the source volume information
set = new StringSet();
set.add(associatedVolumeSource.getNativeGuid());
associatedVolumeMirror.putVolumeInfo(
SupportedVolumeInformation.VPLEX_NATIVE_MIRROR_SOURCE_VOLUME.toString(), set);
associatedVolumeMirror.putVolumeInfo(
SupportedVolumeInformation.VPLEX_BACKEND_CLUSTER_ID.name(),
clusterIds);
// 5. need to go ahead and persist any changes to backend volume info
_dbClient.updateObject(associatedVolumeSource);
_dbClient.updateObject(associatedVolumeMirror);
} else {
String reason = "couldn't find all associated device components in mirror device: ";
reason += " associatedVolumeSource is " + associatedVolumeSource;
reason += " and associatedVolumeMirror is " + associatedVolumeMirror;
_logger.error(reason);
throw VPlexApiException.exceptions.backendIngestionContextLoadFailure(reason);
}
}
}
}
_logger.info("unmanaged mirrors found: " + unmanagedMirrors);
_tracker.fetchMirrors = System.currentTimeMillis() - start;
if (!unmanagedMirrors.isEmpty()) {
StringSet mirrorEntries = new StringSet();
for (Entry<UnManagedVolume, String> mirrorEntry : unmanagedMirrors.entrySet()) {
mirrorEntries.add(mirrorEntry.getKey().getNativeGuid() + "=" + mirrorEntry.getValue());
}
if (mirrorEntries != null && !mirrorEntries.isEmpty()) {
_logger.info("setting VPLEX_MIRROR_MAP: " + mirrorEntries);
_unmanagedVirtualVolume.putVolumeInfo(SupportedVolumeInformation.VPLEX_MIRROR_MAP.name(), mirrorEntries);
}
// need to update the backend volumes because a mirror target shouldn't be considered a direct backend volume
updateUnmanagedBackendVolumesInParent();
}
return unmanagedMirrors;
}
/**
* Finds the backend associated UnManagedVolume for the given VPlexDeviceInfo
* object. It does this by finding the overlap between the context paths
* of the VPlexDeviceInfo and VPlexStorageVolumeInfo objects and then using
* the WWN to locate the related UnManagedVolume.
*
* @param device the VPlexDeviceInfo object to search for
* @return an UnManagedVolume matching the VPlexDeviceInfo.
*/
private UnManagedVolume getAssociatedVolumeForComponentDevice(VPlexDeviceInfo device) {
String devicePath = device.getPath();
_logger.info("associated volume device context path: " + devicePath);
for (Entry<String, VPlexStorageVolumeInfo> entry : getBackendVolumeWwnToInfoMap().entrySet()) {
String storageVolumePath = entry.getValue().getPath();
_logger.info("\tstorage volume context path: " + storageVolumePath);
// context paths should overlap if the device contains the storage volume...
if (null != storageVolumePath && storageVolumePath.startsWith(devicePath)) {
_logger.info("\t\tthis storage volume is a match, trying to find unmanaged backend volume");
for (UnManagedVolume vol : getUnmanagedBackendVolumes()) {
_logger.info("\t\t\tlooking at " + vol.getNativeGuid());
if (vol.getWwn().equalsIgnoreCase(entry.getKey())) {
_logger.info("\t\t\t\tit's a match for " + vol.getWwn());
return vol;
}
}
}
}
return null;
}
/**
* Returns the cluster location (i.e., the cluster name) for a given
* VPlexStorageVolumeInfo by searching through each key in the
* DistributedDevicePathToClusterMap for an overlapping VPLEX API
* context path.
*
* @param storageVolume the storage volume to check
* @return a cluster name where the volume is located from the VPLEX
*/
private String getClusterLocationForStorageVolume(VPlexStorageVolumeInfo storageVolume) {
String storageVolumePath = storageVolume.getPath();
for (Entry<String, String> deviceMapEntry : this.getDistributedDevicePathToClusterMap().entrySet()) {
// example storage volume path:
// /distributed-storage/distributed-devices/dd_VAPM00140844986-00904_V000198700412-024D2/
// distributed-device-components/device_V000198700412-024D2/components/
// extent_V000198700412-024D2_1/components/V000198700412-024D2
// is overlapped by (startsWith) device path:
// /distributed-storage/distributed-devices/dd_VAPM00140844986-00904_V000198700412-024D2/
// distributed-device-components/device_V000198700412-024D2
if (storageVolumePath.startsWith(deviceMapEntry.getKey())) {
_logger.info("found cluster {} for distributed component storage volume {}",
deviceMapEntry.getValue(), storageVolume.getName());
// the value here is the cluster-id
return deviceMapEntry.getValue();
}
}
return null;
}
/**
* Creates a Map of cluster name to sorted Map of slot numbers to VPlexDeviceInfos
* for use in describing the layout of VPLEX native mirrors.
*
* @return a Map of cluster id to sorted Map of slot numbers to VPlexDeviceInfos
*/
private Map<String, Map<String, VPlexDeviceInfo>> getMirrorMap() {
if (null != mirrorMap) {
return mirrorMap;
}
// the mirror map is a mapping of:
//
// cluster id (e.g., cluster-1 and cluster-2) to:
// a sorted map of device slot-number to:
// the VPlexDeviceInfo in that slot
// sort of like: Map<ClusterName, Map<SlotNumber, VPlexDeviceInfo>>
//
// if distributed, it assumes only one mirror set
// can be present on each side of the vplex
_logger.info("assembling mirror map");
mirrorMap = new HashMap<String, Map<String, VPlexDeviceInfo>>();
VPlexResourceInfo device = getTopLevelDevice();
if (isLocal() && (device instanceof VPlexDeviceInfo)) {
VPlexDeviceInfo localDevice = ((VPlexDeviceInfo) device);
if (VPlexApiConstants.ARG_GEOMETRY_RAID1.equals(
((VPlexDeviceInfo) device).getGeometry())) {
mirrorMap.put(localDevice.getCluster(), mapDevices(localDevice));
}
} else {
for (VPlexDeviceInfo localDevice : ((VPlexDistributedDeviceInfo) device).getLocalDeviceInfo()) {
if (VPlexApiConstants.ARG_GEOMETRY_RAID1.equals(
((VPlexDistributedDeviceInfo) device).getGeometry())) {
mirrorMap.put(localDevice.getCluster(), mapDevices(localDevice));
}
}
}
_logger.info("mirror map is: " + mirrorMap);
return mirrorMap;
}
/**
* Creates a Map of slot numbers to VPlexDeviceInfo child objects of a
* given top level device VPlexDeviceInfo, for use in creating the
* VPLEX native mirror map.
*
* @param parentDevice the top level device of this virtual volume
*
* @return a Map of slot numbers to VPlexDeviceInfo child objects
*/
private Map<String, VPlexDeviceInfo> mapDevices(VPlexDeviceInfo parentDevice) {
_logger.info("\tmapping device " + parentDevice.getName());
Map<String, VPlexDeviceInfo> mirrorDevices = new TreeMap<String, VPlexDeviceInfo>();
for (VPlexDeviceInfo dev : parentDevice.getChildDeviceInfo()) {
mirrorDevices.put(dev.getSlotNumber(), dev);
}
return mirrorDevices;
}
/**
* Queries the VPLEX API to find the VPlexResourceInfo object representing
* the top-level device of this virtual volume. Can be either a VPlexDistributedDeviceInfo
* or VPlexDeviceInfo object.
*
* @return a VPlexResourceInfo representing the top-level device of this virtual volume
*/
public VPlexResourceInfo getTopLevelDevice() {
if (null != topLevelDevice) {
return topLevelDevice;
}
long start = System.currentTimeMillis();
_logger.info("getting top level device");
topLevelDevice = VPlexControllerUtils.getDeviceInfo(
getSupportingDeviceName(), getLocality(),
getVplexUri(), _dbClient);
_logger.info("top level device is: " + topLevelDevice);
_tracker.fetchTopLevelDevice = System.currentTimeMillis() - start;
return topLevelDevice;
}
/**
* Returns the supporting device name for this virtual volume.
*
* @return the supporting device name for this virtual volume
*/
public String getSupportingDeviceName() {
String deviceName = extractValueFromStringSet(
SupportedVolumeInformation.VPLEX_SUPPORTING_DEVICE_NAME.toString(),
_unmanagedVirtualVolume.getVolumeInformation());
return deviceName;
}
/**
* Returns the locality for this virtual volume.
*
* @return the locality for this virtual volume
*/
public String getLocality() {
String locality = extractValueFromStringSet(
SupportedVolumeInformation.VPLEX_LOCALITY.toString(),
_unmanagedVirtualVolume.getVolumeInformation());
return locality;
}
/**
* Returns the cluster for this virtual volume, if local. Or
* will return just one if it's a distributed volume.
*
* @return the cluster for this virtual volume
*/
public String getClusterName() {
String cluster = extractValueFromStringSet(
SupportedVolumeInformation.VPLEX_CLUSTER_IDS.toString(),
_unmanagedVirtualVolume.getVolumeInformation());
// even if both clusters are listed, returning just one is fine
return cluster;
}
/**
* Returns true if the virtual volume is a local volume.
*
* @return true if the virtual volume is a local volume
*/
public boolean isLocal() {
return VPlexApiConstants.LOCAL_VIRTUAL_VOLUME.equals(getLocality());
}
/**
* Returns true if the virtual volume is a distributed volume.
*
* @return true if the virtual volume is a distributed volume
*/
public boolean isDistributed() {
return !isLocal();
}
/**
* Returns the map of BlockSnapshot instances created during VPLEX backend ingestion.
*
* @return The map of BlockSnapshot instances created during VPLEX backend ingestion.
*/
public Map<String, BlockSnapshot> getCreatedSnapshotMap() {
return createdSnapshotsMap;
}
/**
* Returns the Project to be used for backend resources.
*
* @return the backend Project
*/
public Project getBackendProject() {
return backendProject;
}
/**
* Sets the Project to be used for backend resources.
*
* @param backendProject the backend Project to set
*/
public void setBackendProject(Project backendProject) {
this.backendProject = backendProject;
}
/**
* Returns the Project to be used for front-end resources.
*
* @return the frontend Project
*/
public Project getFrontendProject() {
return frontendProject;
}
/**
* Sets the Project to be used for front-end resources.
*
* @param frontendProject the frontend Project to set
*/
public void setFrontendProject(Project frontendProject) {
this.frontendProject = frontendProject;
}
/**
* Returns whether or not the context is in discovery mode.
* If true, then the VPLEX API will be queried for new data
* regardless of whether or not data is already present in
* the database.
*
* @return true if in discovery mode
*/
public boolean isDiscoveryInProgress() {
return _discoveryInProgress;
}
/**
* Set whether or not the context is in discovery mode.
* If true, then the VPLEX API will be queried for new data
* regardless of whether or not data is already present in
* the database.
*
* @param set the discovery mode flag
*/
public void setDiscoveryInProgress(boolean inDiscoveryInProgress) {
this._discoveryInProgress = inDiscoveryInProgress;
}
/**
* Returns whether or not the context is in Discovery-Only mode.
* If true, then the VPLEX API will NOT be queried for new data
* and only data present in the database can be used for ingestion.
*
* @return whether or not the context is in discovery mode
*/
public boolean isInDiscoveryOnlyMode() {
return _inDiscoveryOnlyMode;
}
/**
* Sets whether or not the context is in Discovery-Only mode.
* If true, then the VPLEX API will NOT be queried for new data
* and only data present in the database can be used for ingestion.
*
* @param inDiscoveryOnlyMode the discovery flag to set
*/
public void setInDiscoveryOnlyMode(boolean inDiscoveryOnlyMode) {
this._inDiscoveryOnlyMode = inDiscoveryOnlyMode;
}
/**
* Returns whether or not the context is being used for ingestion
* as opposed to discovery.
*
* @return true if the context is being used for ingestion
*/
public boolean isIngestionInProgress() {
return _ingestionInProgress;
}
/**
* Sets whether or not the context is being used for ingestion
* as opposed to discovery.
*
* @param ingestionInProgress the ingestion state flag
*/
public void setIngestionInProgress(boolean ingestionInProgress) {
this._ingestionInProgress = ingestionInProgress;
}
/**
* Find backend UnManagedVolumes for the given VPLEX UnManagedVolume.
*
* @param unmanagedVirtualVolume the virtual volume to find backend volumes for
* @param dbClient a reference to the database client
* @return the backend volume(s) for the UnManagedVolume virtual volume
*/
public static List<UnManagedVolume> findBackendUnManagedVolumes(UnManagedVolume unmanagedVirtualVolume, DbClient dbClient) {
List<UnManagedVolume> unmanagedBackendVolumes = null;
StringSet dbBackendVolumes = extractValuesFromStringSet(
SupportedVolumeInformation.VPLEX_BACKEND_VOLUMES.toString(),
unmanagedVirtualVolume.getVolumeInformation());
if (null != dbBackendVolumes && !dbBackendVolumes.isEmpty()) {
List<URI> umvUris = new ArrayList<URI>();
for (String nativeId : dbBackendVolumes) {
_logger.info("\tfound unmanaged backend volume native id " + nativeId);
URIQueryResultList unManagedVolumeList = new URIQueryResultList();
dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getVolumeInfoNativeIdConstraint(nativeId), unManagedVolumeList);
if (unManagedVolumeList.iterator().hasNext()) {
umvUris.add(unManagedVolumeList.iterator().next());
}
}
if (!umvUris.isEmpty()) {
unmanagedBackendVolumes = dbClient.queryObject(UnManagedVolume.class, umvUris, true);
_logger.info("\treturning unmanaged backend volume objects: " + unmanagedBackendVolumes);
return unmanagedBackendVolumes;
}
}
return unmanagedBackendVolumes;
}
/**
* Copied from PropertySetterUtil, which is in apisvc and
* can't be accessed from controllersvc.
*/
public static String extractValueFromStringSet(String key, StringSetMap volumeInformation) {
try {
StringSet availableValueSet = volumeInformation.get(key);
if (null != availableValueSet) {
for (String value : availableValueSet) {
return value;
}
}
} catch (Exception e) {
_logger.error(e.getMessage(), e);
}
return null;
}
/**
* Copied from PropertySetterUtil, which is in apisvc and
* can't be accessed from controllersvc.
*/
public static StringSet extractValuesFromStringSet(String key, StringSetMap volumeInformation) {
try {
StringSet returnSet = new StringSet();
StringSet availableValueSet = volumeInformation.get(key);
if (null != availableValueSet) {
for (String value : availableValueSet) {
returnSet.add(value);
}
}
return returnSet;
} catch (Exception e) {
_logger.error(e.getMessage(), e);
}
return null;
}
/**
* Copied from VolumeIngestionUtil, which is in apisvc and
* can't be accessed from controllersvc.
*/
public List<UnManagedVolume> getUnManagedSnaphots(UnManagedVolume unManagedVolume) {
List<UnManagedVolume> snapshots = new ArrayList<UnManagedVolume>();
_logger.info("checking for snapshots related to unmanaged volume " + unManagedVolume.getLabel());
if (checkUnManagedVolumeHasReplicas(unManagedVolume)) {
StringSet snapshotNativeIds = extractValuesFromStringSet(
SupportedVolumeInformation.SNAPSHOTS.toString(),
unManagedVolume.getVolumeInformation());
List<URI> snapshotUris = new ArrayList<URI>();
if (null != snapshotNativeIds && !snapshotNativeIds.isEmpty()) {
for (String nativeId : snapshotNativeIds) {
_logger.info(" found snapshot native id " + nativeId);
URIQueryResultList unManagedVolumeList = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getVolumeInfoNativeIdConstraint(nativeId), unManagedVolumeList);
if (unManagedVolumeList.iterator().hasNext()) {
snapshotUris.add(unManagedVolumeList.iterator().next());
}
}
}
if (!snapshotUris.isEmpty()) {
snapshots = _dbClient.queryObject(UnManagedVolume.class, snapshotUris, true);
_logger.info(" returning snapshot objects: " + snapshots);
}
}
return snapshots;
}
/**
* Copied from VolumeIngestionUtil, which is in apisvc and
* can't be accessed from controllersvc.
*/
public List<UnManagedVolume> getUnManagedClones(UnManagedVolume unManagedVolume) {
List<UnManagedVolume> clones = new ArrayList<UnManagedVolume>();
_logger.info("checking for clones (full copies) related to unmanaged volume " + unManagedVolume.getLabel());
if (checkUnManagedVolumeHasReplicas(unManagedVolume)) {
StringSet cloneNativeIds = extractValuesFromStringSet(
SupportedVolumeInformation.FULL_COPIES.toString(),
unManagedVolume.getVolumeInformation());
List<URI> cloneUris = new ArrayList<URI>();
if (null != cloneNativeIds && !cloneNativeIds.isEmpty()) {
for (String nativeId : cloneNativeIds) {
_logger.info("\tfound clone native id " + nativeId);
URIQueryResultList unManagedVolumeList = new URIQueryResultList();
_dbClient.queryByConstraint(AlternateIdConstraint.Factory
.getVolumeInfoNativeIdConstraint(nativeId), unManagedVolumeList);
if (unManagedVolumeList.iterator().hasNext()) {
cloneUris.add(unManagedVolumeList.iterator().next());
}
}
}
if (!cloneUris.isEmpty()) {
clones = _dbClient.queryObject(UnManagedVolume.class, cloneUris, true);
_logger.info("\treturning clone objects: " + clones);
}
}
return clones;
}
/**
* Copied from VolumeIngestionUtil, which is in apisvc and
* can't be accessed from controllersvc.
*/
public boolean checkUnManagedVolumeHasReplicas(UnManagedVolume unManagedVolume) {
StringMap unManagedVolumeCharacteristics = unManagedVolume.getVolumeCharacterstics();
String volumeHasReplicas = unManagedVolumeCharacteristics
.get(SupportedVolumeCharacterstics.HAS_REPLICAS.toString());
String volumeHasRemoteReplicas = unManagedVolumeCharacteristics
.get(SupportedVolumeCharacterstics.REMOTE_MIRRORING.toString());
if (null != volumeHasReplicas
&& Boolean.parseBoolean(volumeHasReplicas)
|| (null != volumeHasRemoteReplicas && Boolean
.parseBoolean(volumeHasRemoteReplicas))) {
return true;
}
return false;
}
/**
* Returns a Map of backend supporting device name
* to the UnManagedVolume that contains it. This is
* necessary because there is no way to query the values
* in a StringSetMap in the database. This is used for
* Full clone (i.e. virtual volume clone) detection.
*
* @return a Map of backend supporting device name to its UnManagedVolume
*/
public Map<String, URI> getVplexDeviceToUnManagedVolumeMap() {
URI vplexUri = getVplexUri();
Iterator<UnManagedVolume> allUnmanagedVolumes = null;
long dingleTimer = new Date().getTime();
Map<String, URI> deviceToUnManagedVolumeMap = new HashMap<String, URI>();
List<URI> storageSystem = new ArrayList<URI>();
storageSystem.add(vplexUri);
try {
List<URI> ids = _dbClient.queryByType(UnManagedVolume.class, true);
List<String> fields = new ArrayList<String>();
fields.add("storageDevice");
fields.add("volumeInformation");
allUnmanagedVolumes = _dbClient.queryIterativeObjectFields(UnManagedVolume.class, fields, ids);
} catch (Exception e) {
// have to do this because the database sometimes returns UnManagedVolume
// objects that no longer exist and are null
_logger.warn("Exception caught:", e);
}
if (null != allUnmanagedVolumes) {
while (allUnmanagedVolumes.hasNext()) {
try {
UnManagedVolume vol = allUnmanagedVolumes.next();
if (vol.getStorageSystemUri().equals(vplexUri)) {
String supportingDeviceName =
extractValueFromStringSet(
SupportedVolumeInformation.VPLEX_SUPPORTING_DEVICE_NAME.toString(),
vol.getVolumeInformation());
if (null != supportingDeviceName) {
deviceToUnManagedVolumeMap.put(supportingDeviceName, vol.getId());
}
}
} catch (NoSuchElementException ex) {
// have to do this because the database sometimes returns UnManagedVolume
// objects that no longer exist and are null
_logger.warn("for some reason the database returned nonsense: "
+ ex.getLocalizedMessage());
}
}
} else {
throw VPlexApiException.exceptions.backendIngestionContextLoadFailure(
"could not load deviceToUnManagedVolumeMap");
}
_logger.info("creating deviceToUnManagedVolumeMap took {} ms", new Date().getTime() - dingleTimer);
return deviceToUnManagedVolumeMap;
}
/**
* 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 to VPLEX cluster names
*/
public Map<String, String> getDistributedDevicePathToClusterMap() {
if (null == distributedDevicePathToClusterMap) {
distributedDevicePathToClusterMap =
VPlexControllerUtils.getDistributedDevicePathToClusterMap(
getVplexUri(), _dbClient);
}
return distributedDevicePathToClusterMap;
}
/**
* Sets the distributed device path to cluster Map. This can be used to
* cache this Map from the outside when iterating through a lot of
* VplexBackendIngestionContexts (see VPlexCommunicationInterface.discover).
*
* @param distributedDevicePathToClusterMap the distributed device path to cluster Map
*/
public void setDistributedDevicePathToClusterMap(Map<String, String> distributedDevicePathToClusterMap) {
this.distributedDevicePathToClusterMap = distributedDevicePathToClusterMap;
}
/**
* Returns the URI of the VPLEX containing the UnManagedVolume of this context.
*
* @return a VPLEX device URI
*/
public URI getVplexUri() {
return getUnmanagedVirtualVolume().getStorageSystemUri();
}
/**
* Validates the structure of the supporting device for acceptable structures
* that can be ingested.
*/
public void validateSupportingDeviceStructure() {
_logger.info("validating the supporting device structure of " + getSupportingDeviceName());
VPlexControllerUtils.validateSupportingDeviceStructure(
getSupportingDeviceName(), getVplexUri(), _dbClient);
}
/**
* Returns the performance report string.
*
* @return the performance report string
*/
public String getPerformanceReport() {
return _tracker.getPerformanceReport();
}
/**
* Simple private inner class to hold performance data for this context.
*/
private class BackendDiscoveryPerformanceTracker {
public long startTime = new Date().getTime();
public long fetchBackendVolumes = 0;
public long fetchSnapshots = 0;
public long fetchBackendOnlyClones = 0;
public long fetchVplexClones = 0;
public long fetchMirrors = 0;
public long fetchTopLevelDevice = 0;
public String getPerformanceReport() {
StringBuilder report = new StringBuilder("\n\nBackend Discovery Performance Report\n");
report.append("\tvolume name: ").append(_unmanagedVirtualVolume.getLabel()).append("\n");
report.append("\ttotal discovery time: ").append(System.currentTimeMillis() - startTime).append("ms\n");
report.append("\tfetch backend volumes: ").append(fetchBackendVolumes).append("ms\n");
report.append("\tfetch snapshots: ").append(fetchSnapshots).append("ms\n");
report.append("\tfetch backend clones: ").append(fetchBackendOnlyClones).append("ms\n");
report.append("\tfetch full clones: ").append(fetchVplexClones).append("ms\n");
report.append("\tfetch mirrors: ").append(fetchMirrors).append("ms\n");
report.append("\tfetch top-level device: ").append(fetchTopLevelDevice).append("ms\n");
return report.toString();
}
}
}