/* * Copyright (c) 2016 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.validators.vplex; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.VirtualPool.SystemType; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.VplexMirror; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.util.VPlexDrillDownParser; import com.emc.storageos.util.VPlexDrillDownParser.NodeType; import com.emc.storageos.util.VPlexUtil; import com.emc.storageos.volumecontroller.impl.validators.ValCk; import com.emc.storageos.volumecontroller.impl.validators.ValidatorConfig; import com.emc.storageos.volumecontroller.impl.validators.ValidatorLogger; import com.emc.storageos.vplex.api.VPlexApiClient; import com.emc.storageos.vplex.api.VPlexApiConstants; import com.emc.storageos.vplex.api.VPlexApiException; import com.emc.storageos.vplex.api.VPlexApiFactory; 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; import com.emc.storageos.vplex.api.VPlexVirtualVolumeInfo; import com.emc.storageos.vplex.api.clientdata.VolumeInfo; import com.emc.storageos.vplexcontroller.VPlexControllerUtils; public class VplexVolumeValidator extends AbstractVplexValidator { private VPlexApiClient client = null; private Logger log = LoggerFactory.getLogger(VplexVolumeValidator.class); private List<Volume> remediatedVolumes = new ArrayList<Volume>(); public VplexVolumeValidator(DbClient dbClient, ValidatorConfig config, ValidatorLogger logger) { super(dbClient, config, logger); } /** * Validates the given volumes. * * @param storageSystem the VPLEX storage system containing the Volume * @param volumes the list of Volumes to validate * @param delete boolean indicating if this validation is part of a delete operation * @param remediate boolean indicating whether or not remediation should be attempted * @param checks variable list of ValCks * @return a List of any remediated Volumes */ public List<Volume> validateVolumes(StorageSystem storageSystem, List<Volume> volumes, boolean delete, boolean remediate, ValCk... checks) { try { client = VPlexControllerUtils.getVPlexAPIClient(VPlexApiFactory.getInstance(), storageSystem, getDbClient()); for (Volume volume : volumes) { try { log.info(String.format("Validating %s (%s)(%s) checks %s", volume.getLabel(), volume.getNativeId(), volume.getId(), checks.toString())); validateVolume(volume, delete, remediate, checks); } catch (Exception ex) { log.error("Exception validating volume: " + volume.getId(), ex); } } } catch (Exception ex) { log.error("Unexpected exception validating VPLEX: " + storageSystem.getId(), ex); } return remediatedVolumes; } /** * Validates an individual volume. * * @param virtualVolume the Volume to validate * @param delete boolean indicating if this validation is part of a delete operation * @param remediate boolean indicating whether or not remediation should be attempted * @param checks variable list of ValCks */ public void validateVolume(Volume virtualVolume, boolean delete, boolean remediate, ValCk... checks) { List<ValCk> checkList = Arrays.asList(checks); String volumeId = String.format("%s (%s)(%s)", virtualVolume.getLabel(), virtualVolume.getNativeGuid(), virtualVolume.getId()); log.info("Initiating Vplex Volume validation: " + volumeId); VPlexVirtualVolumeInfo vvinfo = null; try { vvinfo = client.findVirtualVolume(virtualVolume.getDeviceLabel(), virtualVolume.getNativeId()); } catch (VPlexApiException ex) { log.info(ex.getMessage()); } if (vvinfo == null) { try { // Didn't find the virtual volume. Look at the storage volume, and from that determine // the deviceName. Then lookup the Device or DistributedDevice and check to see if // the device has been reassigned to a different virtual volume. Volume storageVolume = VPlexUtil.getVPLEXBackendVolume(virtualVolume, true, getDbClient(), false); if (storageVolume != null) { StorageSystem system = getDbClient().queryObject(StorageSystem.class, storageVolume.getStorageController()); // Look up the corresponding device name to our Storage Volume VolumeInfo volumeInfo = new VolumeInfo(system.getNativeGuid(), system.getSystemType(), storageVolume.getWWN().toUpperCase().replaceAll(":", ""), storageVolume.getNativeId(), storageVolume.getThinlyProvisioned().booleanValue(), VPlexControllerUtils.getVolumeITLs(storageVolume)); String deviceName = client.getDeviceForStorageVolume(volumeInfo); log.info("device name is " + deviceName); if (deviceName == null) { if (!delete) { // We didn't find a device name for the storage volume. Error if not deleting. getValidatorLogger().logDiff(volumeId, "Vplex device-name", system.getSerialNumber() + "-" + storageVolume.getNativeId(), ValidatorLogger.NO_MATCHING_ENTRY); return; } } if (null != deviceName && !deviceName.trim().matches(VPlexApiConstants.STORAGE_VOLUME_NOT_IN_USE)) { String volumeType = VPlexApiConstants.LOCAL_VIRTUAL_VOLUME; if (virtualVolume.getAssociatedVolumes() != null && virtualVolume.getAssociatedVolumes().size() > 1) { volumeType = VPlexApiConstants.DISTRIBUTED_VIRTUAL_VOLUME; } VPlexResourceInfo resourceInfo = client.getDeviceStructure(deviceName, volumeType); if (resourceInfo instanceof VPlexDeviceInfo) { // local device VPlexDeviceInfo localDeviceInfo = (VPlexDeviceInfo) resourceInfo; String virtualVolumeName = localDeviceInfo.getVirtualVolume(); if (virtualVolumeName != null && !virtualVolumeName.equals(virtualVolume.getDeviceLabel())) { getValidatorLogger().logDiff(volumeId, "virtual-volume name changed", virtualVolume.getDeviceLabel(), virtualVolumeName); } } else if (resourceInfo instanceof VPlexDistributedDeviceInfo) { VPlexDistributedDeviceInfo distDeviceInfo = (VPlexDistributedDeviceInfo) resourceInfo; String virtualVolumeName = distDeviceInfo.getVirtualVolume(); if (virtualVolumeName != null && !virtualVolumeName.equals(virtualVolume.getDeviceLabel())) { getValidatorLogger().logDiff(volumeId, "virtual-volume name changed", virtualVolume.getDeviceLabel(), virtualVolumeName); } } } } } catch (VPlexApiException ex) { log.error("Unable to determine if VPLEX device reused: " + volumeId, ex); if (getValidatorConfig().isValidationEnabled()) { throw ex; } } if (!delete) { // If we didn't log an error above indicating that the volume was reused, // and we are not deleting, it is still an error. // If we are deleting we won't error if it's just not there. getValidatorLogger().logDiff(volumeId, "virtual-volume", virtualVolume.getDeviceLabel(), ValidatorLogger.NO_MATCHING_ENTRY); } log.info("Vplex Validation complete (no vvinfo found); " + volumeId); return; } if (checkList.contains(ValCk.ID)) { if (!virtualVolume.getDeviceLabel().equals(vvinfo.getName())) { getValidatorLogger().logDiff(volumeId, "native-id", virtualVolume.getNativeId(), vvinfo.getName()); } if (!NullColumnValueGetter.isNullValue(virtualVolume.getWWN()) && vvinfo.getWwn() != null && !virtualVolume.getWWN().equalsIgnoreCase(vvinfo.getWwn())) { getValidatorLogger().logDiff(volumeId, "wwn", virtualVolume.getWWN(), vvinfo.getWwn()); } if (virtualVolume.getAssociatedVolumes() != null && !virtualVolume.getAssociatedVolumes().isEmpty()) { String locality = virtualVolume.getAssociatedVolumes().size() > 1 ? VPlexApiConstants.DISTRIBUTED_VIRTUAL_VOLUME : VPlexApiConstants.LOCAL_VIRTUAL_VOLUME; if (!locality.equalsIgnoreCase(vvinfo.getLocality())) { getValidatorLogger().logDiff(volumeId, "locality", locality, vvinfo.getLocality()); } } } if (checkList.contains(ValCk.VPLEX) && !virtualVolume.isIngestedVolumeWithoutBackend(getDbClient())) { try { String drillDownInfo = client.getDrillDownInfoForDevice(vvinfo.getSupportingDevice()); VPlexDrillDownParser parser = new VPlexDrillDownParser(drillDownInfo); VPlexDrillDownParser.Node root = parser.parse(); boolean distributed = (root.getType() == VPlexDrillDownParser.NodeType.DIST) ? true : false; if (distributed) { List<VPlexDrillDownParser.Node> svols = root.getNodesOfType(NodeType.SVOL); boolean hasMirror = svols.size() > 2; String clusterName = VPlexControllerUtils.getVPlexClusterName(getDbClient(), virtualVolume); for (VPlexDrillDownParser.Node child : root.getChildren()) { if (child.getArg2().equals(clusterName)) { validateStorageVolumes(virtualVolume, volumeId, root.getArg1(), true, child.getArg2(), hasMirror); } } } else { List<VPlexDrillDownParser.Node> svols = root.getNodesOfType(NodeType.SVOL); boolean hasMirror = svols.size() > 1; validateStorageVolumes(virtualVolume, volumeId, root.getArg1(), false, root.getArg2(), hasMirror); } } catch (Exception ex) { getValidatorLogger().logDiff(volumeId, "exception trying to validate storage volumes", virtualVolume.getDeviceLabel(), "N/A"); } } log.info("Vplex Validation complete; " + volumeId); } /** * Validates either storage volumes for either distributed or local vplex volumes. * * @param virtualVolume * -- Vplex virtual volume * @param volumeId * -- identification for log * @param topLevelDeviceName * -- top level VPLEX device name * @param distributed * -- if true VPLEX distributed, false if VPLEX local * @param cluster * cluster-1 or cluster-2 * @param hasMirror * -- if true this cluster has a mirror * @return failed -- If true a discrepancy was detected * */ private boolean validateStorageVolumes(Volume virtualVolume, String volumeId, String topLevelDeviceName, boolean distributed, String cluster, boolean hasMirror) { boolean failed = false; Map<String, VPlexStorageVolumeInfo> wwnToStorageVolumeInfos = client.getStorageVolumeInfoForDevice( topLevelDeviceName, distributed ? VPlexApiConstants.DISTRIBUTED_VIRTUAL_VOLUME : VPlexApiConstants.LOCAL_VIRTUAL_VOLUME, cluster, hasMirror); // Check local volume is present Volume assocVolume = VPlexUtil.getVPLEXBackendVolume(virtualVolume, true, getDbClient()); if (!storageSystemSupportsValidation(assocVolume)) { return failed; } if (!wwnToStorageVolumeInfos.keySet().contains(assocVolume.getWWN())) { getValidatorLogger().logDiff(volumeId, "SOURCE storage volume WWN", assocVolume.getWWN(), wwnToStorageVolumeInfos.keySet().toString()); failed = true; } else { wwnToStorageVolumeInfos.remove(assocVolume.getWWN()); } // Check HA volume is present if (distributed) { assocVolume = VPlexUtil.getVPLEXBackendVolume(virtualVolume, false, getDbClient()); if (!storageSystemSupportsValidation(assocVolume)) { return failed; } if (!wwnToStorageVolumeInfos.keySet().contains(assocVolume.getWWN())) { getValidatorLogger().logDiff(volumeId, "HA storage volume WWN", assocVolume.getWWN(), wwnToStorageVolumeInfos.keySet().toString()); failed = true; } else { wwnToStorageVolumeInfos.remove(assocVolume.getWWN()); } } // Process any mirrors that were found if (virtualVolume.getMirrors() != null) { for (String mirrorId : virtualVolume.getMirrors()) { VplexMirror mirror = getDbClient().queryObject(VplexMirror.class, URI.create(mirrorId)); if (mirror == null || mirror.getInactive() || mirror.getAssociatedVolumes() == null) { // Not fully formed for some reason, skip it continue; } // Now get the underlying Storage Volume for (String mirrorAssociatedId : mirror.getAssociatedVolumes()) { Volume mirrorVolume = getDbClient().queryObject(Volume.class, URI.create(mirrorAssociatedId)); if (!storageSystemSupportsValidation(mirrorVolume)) { return failed; } if (mirrorVolume != null && !NullColumnValueGetter.isNullValue(mirrorVolume.getWWN())) { if (!wwnToStorageVolumeInfos.keySet().contains(mirrorVolume.getWWN())) { getValidatorLogger().logDiff(volumeId, "Mirror WWN", mirrorVolume.getWWN(), wwnToStorageVolumeInfos.keySet().toString()); failed = true; } else { wwnToStorageVolumeInfos.remove(mirrorVolume.getWWN()); } } } } } if (!wwnToStorageVolumeInfos.isEmpty()) { getValidatorLogger().logDiff(volumeId, "Extra storage volumes found", ValidatorLogger.NO_MATCHING_ENTRY, wwnToStorageVolumeInfos.keySet().toString()); failed = true; } return failed; } /** * Returns true if a Storage Volume is from a Storage System type that has been validated to work * with the validation logic. This will normally be only EMC arrays. * @param volume -- Volume (Storage Volume for the VPlex) * @return true if Storage Volume validation should be done, false otherwise */ private boolean storageSystemSupportsValidation(Volume volume) { if (volume == null) { return false; } StorageSystem system = getDbClient().queryObject(StorageSystem.class, volume.getStorageController()); SystemType type = SystemType.valueOf(system.getSystemType()); if (type == SystemType.vmax || type == SystemType.vnxblock || type == SystemType.vnxe || type == SystemType.xtremio || type == SystemType.unity) { return true; } return false; } }