/* * Copyright (c) 2016 EMC Corporation * All Rights Reserved */ package com.emc.storageos.vplexdbckr; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import java.util.HashMap; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.vplex.api.VPlexApiClient; import com.emc.storageos.vplex.api.VPlexApiConstants; import com.emc.storageos.vplex.api.VPlexApiFactory; 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.db.client.model.VplexMirror; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.vplexcontroller.VPlexControllerUtils; import com.emc.storageos.vplex.api.VPlexStorageViewInfo; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.StringMap; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.blockorchestrationcontroller.VolumeDescriptor; import com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController; import com.emc.storageos.model.block.VolumeDeleteTypeEnum; import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.client.model.BlockMirror; /** * A single bean instance of this class is started from the Spring configuration. * The various static variables such as dbClient and vplexApiFactory are injected by Spring. * The public constructor saves the instance so it can be returned by a static method. * * VplexDBCkr provides methods for accessing the database, the vplex, and performing the * required checks. * */ public class VplexDBCkr { Logger log = LoggerFactory.getLogger(VplexDBCkr.class); private static VplexDBCkr bean = null; private static DbClient dbClient = null; private static VPlexApiFactory vplexApiFactory = null; public void cleanupForViPROnlyDelete(List<VolumeDescriptor> volumeDescriptors) { // Remove volumes from ExportGroup(s) and ExportMask(s). List<URI> volumeURIs = VolumeDescriptor.getVolumeURIs(volumeDescriptors); for (URI volumeURI : volumeURIs) { cleanBlockObjectFromExports(volumeURI, true); } // Clean up the relationship between vplex volumes that are full // copies and and their source vplex volumes. List<VolumeDescriptor> vplexVolumeDescriptors = VolumeDescriptor .getDescriptors(volumeDescriptors, VolumeDescriptor.Type.VPLEX_VIRT_VOLUME); List<URI> vplexvolumeURIs = VolumeDescriptor.getVolumeURIs(volumeDescriptors); for (URI volumeURI : vplexvolumeURIs) { Volume volume = dbClient.queryObject(Volume.class, volumeURI); URI sourceVolumeURI = volume.getAssociatedSourceVolume(); if (!NullColumnValueGetter.isNullURI(sourceVolumeURI)) { // The volume being removed is a full copy. Make sure the copies // list of the source no longer references this volume. Note // that it is possible that the source was already deleted but // we left the source URI set in the copy, so one could always // know the source of the copy. So, check for a null source // volume. Volume sourceVolume = dbClient.queryObject(Volume.class, sourceVolumeURI); if (sourceVolume != null) { StringSet fullCopyIds = sourceVolume.getFullCopies(); if (fullCopyIds.contains(volumeURI.toString())) { fullCopyIds.remove(volumeURI.toString()); dbClient.updateObject(sourceVolume); } } } } } public void cleanBlockObjectFromExports(URI boURI, boolean addToExisting) { writeLog(String.format("Cleaning block object from exports %s", boURI)); Map<URI, ExportGroup> exportGroupMap = new HashMap<URI, ExportGroup>(); Map<URI, ExportGroup> updatedExportGroupMap = new HashMap<URI, ExportGroup>(); Map<String, ExportMask> exportMaskMap = new HashMap<String, ExportMask>(); Map<String, ExportMask> updatedExportMaskMap = new HashMap<String, ExportMask>(); BlockObject bo = BlockObject.fetch(dbClient, boURI); URIQueryResultList exportGroupURIs = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(boURI), exportGroupURIs); for (URI exportGroupURI : exportGroupURIs) { writeLog(String.format("Cleaning block object from export group %s",exportGroupURI)); ExportGroup exportGroup = null; if (exportGroupMap.containsKey(exportGroupURI)) { exportGroup = exportGroupMap.get(exportGroupURI); } else { exportGroup = dbClient.queryObject(ExportGroup.class, exportGroupURI); exportGroupMap.put(exportGroupURI, exportGroup); } if (exportGroup.hasBlockObject(boURI)) { writeLog(String.format("Removing block object from export group")); exportGroup.removeVolume(boURI); if (!updatedExportGroupMap.containsKey(exportGroupURI)) { updatedExportGroupMap.put(exportGroupURI, exportGroup); } } List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(dbClient, exportGroup); for (ExportMask exportMask : exportMasks) { if (exportMask.hasVolume(boURI)) { writeLog(String.format("Cleaning block object from export mask %s", exportMask.getId().toString())); StringMap exportMaskVolumeMap = exportMask.getVolumes(); String hluStr = exportMaskVolumeMap.get(boURI.toString()); exportMask.removeVolume(boURI); exportMask.removeFromUserCreatedVolumes(bo); // Add this volume to the existing volumes map for the // mask, so that if the last ViPR created volume goes // away, the physical mask will not be deleted. if (addToExisting) { writeLog(String.format("Adding to existing volumes")); exportMask.addToExistingVolumesIfAbsent(bo, hluStr); } if (!updatedExportMaskMap.containsKey(exportMask.getId().toString())) { updatedExportMaskMap.put(exportMask.getId().toString(), exportMask); } } } } if (!updatedExportGroupMap.isEmpty()) { List<ExportGroup> updatedExportGroups = new ArrayList<ExportGroup>( updatedExportGroupMap.values()); dbClient.updateObject(updatedExportGroups); } if (!updatedExportMaskMap.isEmpty()) { List<ExportMask> updatedExportMasks = new ArrayList<ExportMask>( updatedExportMaskMap.values()); dbClient.updateObject(updatedExportMasks); } } public List<ExportMask> isVolumeExported(URI boURI) { writeLog(String.format("checking block object belong to any export groups %s", boURI)); boolean isExported = false; Map<String, ExportMask> exportMaskMap = new HashMap<String, ExportMask>(); URIQueryResultList exportGroupURIs = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getBlockObjectExportGroupConstraint(boURI), exportGroupURIs); isExported = exportGroupURIs.iterator().hasNext(); if (isExported == false) { return null; } for (URI exportGroupURI : exportGroupURIs) { //writeLog(String.format("Cleaning block object from export group %s",exportGroupURI)); ExportGroup exportGroup = null; exportGroup = dbClient.queryObject(ExportGroup.class, exportGroupURI); if (!exportGroup.hasBlockObject(boURI)) { continue; } List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(dbClient, exportGroup); for (ExportMask exportMask : exportMasks) { if (exportMask.hasVolume(boURI)) { if (!exportMaskMap.containsKey(exportMask.getId().toString())) { exportMaskMap.put(exportMask.getId().toString(), exportMask); } } } } if (!exportMaskMap.isEmpty()) { List<ExportMask> updatedExportMasks = new ArrayList<ExportMask>(exportMaskMap.values()); return updatedExportMasks; } else { return null; } } public List<VolumeDescriptor> getDescriptorsForVolumesToBeDeleted(URI systemURI, List<URI> volumeURIs, String deletionType) { List<VolumeDescriptor> volumeDescriptors = new ArrayList<VolumeDescriptor>(); for (URI volumeURI : volumeURIs) { Volume volume = dbClient.queryObject(Volume.class, volumeURI); VolumeDescriptor descriptor = new VolumeDescriptor(VolumeDescriptor.Type.VPLEX_VIRT_VOLUME, systemURI, volumeURI, null, null); volumeDescriptors.add(descriptor); // Add a descriptor for each of the associated volumes. if (!volume.isIngestedVolumeWithoutBackend(dbClient) && (null != volume.getAssociatedVolumes())) { for (String assocVolId : volume.getAssociatedVolumes()) { Volume assocVolume = dbClient.queryObject(Volume.class, URI.create(assocVolId)); if (null != assocVolume && !assocVolume.getInactive() && assocVolume.getNativeId() != null) { VolumeDescriptor assocDesc = new VolumeDescriptor(VolumeDescriptor.Type.BLOCK_DATA, assocVolume.getStorageController(), assocVolume.getId(), null, null); volumeDescriptors.add(assocDesc); } } // If there were any Vplex Mirrors, add a descriptors for them. addDescriptorsForVplexMirrors(volumeDescriptors, volume); } } return volumeDescriptors; } public void addDescriptorsForVplexMirrors(List<VolumeDescriptor> descriptors, Volume vplexVolume) { if (vplexVolume.getMirrors() != null && vplexVolume.getMirrors().isEmpty() == false) { for (String mirrorId : vplexVolume.getMirrors()) { VplexMirror mirror = dbClient.queryObject(VplexMirror.class, URI.create(mirrorId)); if (mirror != null && !mirror.getInactive()) { if (null != mirror.getAssociatedVolumes()) { for (String assocVolumeId : mirror.getAssociatedVolumes()) { Volume volume = dbClient.queryObject(Volume.class, URI.create(assocVolumeId)); if (volume != null && !volume.getInactive()) { VolumeDescriptor volDesc = new VolumeDescriptor(VolumeDescriptor.Type.BLOCK_DATA, volume.getStorageController(), URI.create(assocVolumeId), null, null); descriptors.add(volDesc); } } } } } } } public void checkVolumesOnVplex(URI vplexSystemURI, boolean deleteInvalidVolumes) { URIQueryResultList result = new URIQueryResultList(); List<URI> deletevirtualvolumeURIs = new ArrayList<URI>(); int nerrors = 0; int invalidVolumeCount = 0; dbClient.queryByConstraint( ContainmentConstraint.Factory.getStorageDeviceVolumeConstraint(vplexSystemURI), result); Iterator<URI> iter = result.iterator(); VPlexApiClient client = getVPlexApiClient(vplexSystemURI); // Get all the virtual volumes. We elect for shallow here as it's quicker- // we will spend time below getting details. writeLog("Retrieving all virtual volumes... this will take some time..."); Map<String, VPlexVirtualVolumeInfo> vvInfoMap = client.getVirtualVolumes(true); List<VPlexStorageViewInfo> storageViews = client.getStorageViewsLite(); writeLog("... done"); try { while(iter.hasNext()) { Volume volume = dbClient.queryObject(Volume.class, iter.next()); if (volume == null || volume.getInactive()) { continue; } writeLog(String.format("Checking volume %s (%s)", volume.getLabel(), volume.getDeviceLabel())); if (volume.getAssociatedVolumes() == null || volume.getAssociatedVolumes().isEmpty()) { writeLog(String.format("Volume %s (%s) has no associated volumes... skipping", volume.getLabel(), volume.getDeviceLabel())); continue; } VPlexVirtualVolumeInfo vvInfo = vvInfoMap.get(volume.getDeviceLabel()); if (vvInfo == null) { writeLog(String.format("ERROR: Volume %s (%s) had no VirtualVolumeInfo in VPlex", volume.getLabel(), volume.getDeviceLabel())); deletevirtualvolumeURIs.add(volume.getId()); nerrors++; invalidVolumeCount++; continue; } if ((null != vvInfo.getWwn()) && (null != volume.getWWN())) { if (vvInfo.getName().equals(volume.getDeviceLabel())) { if (vvInfo.getWwn().toUpperCase().equals(volume.getWWN().toUpperCase())) { writeLog(String.format("Virtual Volume %s wwn %s matches VPLEX", vvInfo.getName(), vvInfo.getWwn())); } else { writeLog(String.format("ERROR: Virtual Volume %s wwn %s in VPLEX mismatch with viprdb %s", vvInfo.getName(), vvInfo.getWwn(), volume.getWWN())); deletevirtualvolumeURIs.add(volume.getId()); invalidVolumeCount++; nerrors++; } } } StringSet wwns = new StringSet(); for (String cluster : vvInfo.getClusters()) { Map<String, VPlexStorageVolumeInfo> svInfoMap = client.getStorageVolumeInfoForDevice( vvInfo.getSupportingDevice(), vvInfo.getLocality(), cluster, false); for (String wwn : svInfoMap.keySet()) { //writeLog("adding wwn " + wwn.toUpperCase()); wwns.add(wwn.toUpperCase()); VPlexStorageVolumeInfo svInfo = svInfoMap.get(wwn); writeLog(String.format("StorageVolume wwn %s name %s cluster %s", wwn, svInfo.getName(), cluster)); } } // Now check associated volumes against the wwns. for (String associatedVolume : volume.getAssociatedVolumes()) { Volume assocVolume = dbClient.queryObject(Volume.class, URI.create(associatedVolume)); if (assocVolume == null) { writeLog("Associated volunme not found in database... skipping: " + associatedVolume); continue; } if (wwns.contains(assocVolume.getWWN().toUpperCase())) { writeLog(String.format("Volume %s wwn %s matches VPLEX", assocVolume.getLabel(), assocVolume.getWWN())); } else { writeLog(String.format("ERROR: Volume %s wwn %s is not present in VPLEX", assocVolume.getLabel(), assocVolume.getWWN())); nerrors++; } } List<ExportMask> exportMaskListInDB = isVolumeExported(volume.getId()); if (null != exportMaskListInDB) { for (ExportMask exportMaskInDB : exportMaskListInDB) { boolean found = false; boolean storageviewfound = false; for (VPlexStorageViewInfo storageView : storageViews) { if (storageView.getName().equals(exportMaskInDB.getMaskName())) { storageviewfound = true; for (String volumeNameStr : storageView.getVirtualVolumes()) { String[] tokens = volumeNameStr.split(","); String volumeName = tokens[1]; if (volumeName.equals(volume.getDeviceLabel())) { found = true; break; } } if(!found) { writeLog(String.format("ERROR: volume %s is in exportmask %s in viprdb but not in vplex storageview %s",volume.getDeviceLabel(),exportMaskInDB.getMaskName(),storageView.getName())); nerrors++; } break; } } if (!storageviewfound) { writeLog(String.format("ERROR: volume %s is in exportmask %s in viprdb but storageview not found in vplex",volume.getDeviceLabel(),exportMaskInDB.getMaskName())); nerrors++; } } } for (VPlexStorageViewInfo storageView : storageViews) { writeLog(String.format("Checking Storageview %s",storageView.getName())); for (String volumeNameStr : storageView.getVirtualVolumes()) { String[] tokens = volumeNameStr.split(","); String volumeName = tokens[1]; if (volumeName.equals(volume.getDeviceLabel())) { boolean storageviewfound = false; if (null != exportMaskListInDB) { for (ExportMask exportMaskInDB : exportMaskListInDB) { if (storageView.getName().equals(exportMaskInDB.getMaskName())) { storageviewfound = true; break; } } } if (!storageviewfound) { writeLog(String.format("ERROR: volume %s is in vplex storageview %s but not in viprdb exportmask",volumeName,storageView.getName())); nerrors++; } } } } } if (deleteInvalidVolumes) { writeLog("deleting invalid volumes"); // deleting virtual volumes that no longer exist in vplex List<VolumeDescriptor> volumeDescriptors = getDescriptorsForVolumesToBeDeleted(vplexSystemURI, deletevirtualvolumeURIs, VolumeDeleteTypeEnum.VIPR_ONLY.name()); cleanupForViPROnlyDelete(volumeDescriptors); // Mark them inactive. Note that some of the volumes may be mirrors, // which have a different database type. List<VolumeDescriptor> descriptorsForMirrors = VolumeDescriptor.getDescriptors( volumeDescriptors, VolumeDescriptor.Type.BLOCK_MIRROR); dbClient.markForDeletion(dbClient.queryObject(BlockMirror.class, VolumeDescriptor.getVolumeURIs(descriptorsForMirrors))); List<VolumeDescriptor> descriptorsForVolumes = VolumeDescriptor.filterByType( volumeDescriptors, null, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_MIRROR }); dbClient.markForDeletion(dbClient.queryObject(Volume.class, VolumeDescriptor.getVolumeURIs(descriptorsForVolumes))); // Update the task status for each volume for (URI volumeURI : deletevirtualvolumeURIs) { Volume volume = dbClient.queryObject(Volume.class, volumeURI); dbClient.updateObject(volume); } } } catch (Exception e) { writeLog(String.format("Exception: while verifying virtual volumes", e)); } //List<URI> maskUrislist = new ArrayList<URI>();; //maskUrislist.add(URI.create("urn:storageos:ExportMask:3742e612-cc93-422b-a1a5-43490e0fe8ea:vdc1")); //for (URI mskUri : maskUrislist) { //boolean found = false; //ExportMask exportMaskUri = dbClient.queryObject(ExportMask.class, mskUri); //if (exportMaskUri == null || exportMaskUri.getInactive()) { // continue; //} //writeLog(String.format("exportMaskUri in ViPR DB is %s", exportMaskUri.getMaskName())); //for (VPlexStorageViewInfo storageView : storageViews) { //if (storageView.getName().equals(exportMaskUri.getMaskName())) { //found = true; //} //} //if(!found) { // writeLog(String.format("ERROR: exportMask not found in vplex %s",exportMaskUri.getMaskName())); //nerrors++; //} // } writeLog("Total errors for this VPLEX: " + nerrors); } /** * Retrieves VPLEX systems from the database. * @return List<StorageSystem> */ List<StorageSystem> getVPlexSystems() { List<StorageSystem> vplexSystems = new ArrayList<StorageSystem>(); List<URI> storageSystems = dbClient.queryByType(StorageSystem.class, true); for (URI storageSystemUri : storageSystems) { StorageSystem system = dbClient.queryObject(StorageSystem.class, storageSystemUri); if (DiscoveredDataObject.Type.vplex.name().equals(system.getSystemType())) { log.info("VPLEX system: " + system.getLabel()); vplexSystems.add(system); } } return vplexSystems; } /** * Returns a VPlexApiClient for the system with the specified URI. * @param vplexUri * @return */ public VPlexApiClient getVPlexApiClient(URI vplexUri) { try { if (vplexApiFactory == null) { vplexApiFactory = VPlexApiFactory.getInstance(); } VPlexApiClient client = VPlexControllerUtils.getVPlexAPIClient(vplexApiFactory, vplexUri, dbClient); return client; } catch (Exception ex) { log.error("Could not get VPlexApiClient"); System.out.println("Could not connect to VPLEX: " + vplexUri); System.exit(2);; } return null; } public void writeLog(String s) { System.out.println(s); log.info(s); } public void dbClientStart() { dbClient.start(); } public VplexDBCkr() { bean = this; } public DbClient getDbClient() { return dbClient; } public void setDbClient(DbClient dbClient) { this.dbClient = dbClient; } public static VplexDBCkr getBean() { return bean; } }