/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.recoverpoint.utils; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.fapiclient.ws.BookmarkConsolidationPolicy; import com.emc.fapiclient.ws.ClusterUID; import com.emc.fapiclient.ws.ConsistencyGroupCopySettings; import com.emc.fapiclient.ws.ConsistencyGroupCopySnapshots; import com.emc.fapiclient.ws.ConsistencyGroupCopyUID; import com.emc.fapiclient.ws.ConsistencyGroupSettings; import com.emc.fapiclient.ws.ConsistencyGroupUID; import com.emc.fapiclient.ws.FunctionalAPIActionFailedException_Exception; import com.emc.fapiclient.ws.FunctionalAPIImpl; import com.emc.fapiclient.ws.FunctionalAPIInternalError_Exception; import com.emc.fapiclient.ws.PipeState; import com.emc.fapiclient.ws.ReplicationSetSettings; import com.emc.fapiclient.ws.Snapshot; import com.emc.fapiclient.ws.SnapshotConsistencyType; import com.emc.fapiclient.ws.UserVolumeSettings; import com.emc.storageos.recoverpoint.exceptions.RecoverPointException; import com.emc.storageos.recoverpoint.impl.RecoverPointClient; import com.emc.storageos.recoverpoint.impl.RecoverPointClient.RecoverPointReturnCode; import com.emc.storageos.recoverpoint.objectmodel.RPBookmark; import com.emc.storageos.recoverpoint.objectmodel.RPConsistencyGroup; import com.emc.storageos.recoverpoint.objectmodel.RPCopy; import com.emc.storageos.recoverpoint.requests.CreateBookmarkRequestParams; import com.emc.storageos.recoverpoint.responses.CreateBookmarkResponse; public class RecoverPointBookmarkManagementUtils { private static Logger logger = LoggerFactory.getLogger(RecoverPointClient.class); private final static int numMicroSecondsInMilli = 1000; private final static int numMillisInSecond = 1000; public enum RecoverPointCopyType { CDP_PROTECTION, CRR_PROTECTION, CLR_PROTECTION, MULTISITE_PROTECTION, UNKNOWN_PROTECTION } /** * Take a list of WWNs for a RecoverPoint appliance and return consistency group information for the WWNs * * @param impl - RP handle to perform RP operations * @param request - Input request of WWNs * @param unmappedWWNs - WWNs that could not be mapped to a consistency group * * @return WWN to consistency group mappings * * @throws RecoverPointException **/ public Map<String, RPConsistencyGroup> mapCGsForWWNs(FunctionalAPIImpl impl, CreateBookmarkRequestParams request, Set<String> unmappedWWNs) throws RecoverPointException { try { Set<String> wwnList = request.getVolumeWWNSet(); if (wwnList.isEmpty()) { logger.error("Input WWN list size is 0"); return null; } Map<String, RPConsistencyGroup> returnMap = new HashMap<String, RPConsistencyGroup>(); Set<String> wwnListCopy = new HashSet<String>(); for (String wwn : wwnList) { wwnListCopy.add(wwn.toLowerCase(Locale.ENGLISH)); logger.info("Mapping source WWN " + wwn.toLowerCase(Locale.ENGLISH) + " to RecoverPoint CG"); } List<ConsistencyGroupSettings> cgSettings = impl.getAllGroupsSettings(); RPConsistencyGroup rpCG = null; for (ConsistencyGroupSettings cgSetting : cgSettings) { for (ReplicationSetSettings rsSetting : cgSetting.getReplicationSetsSettings()) { // Only get the unique volumes from a replication set. In MetroPoint, a replication set will list the source volume // twice. This is because in MetroPoint each VPLEX leg is considered a copy but the WWN/volume is the same. Set<UserVolumeSettings> uvSettings = new HashSet<UserVolumeSettings>(); uvSettings.addAll(rsSetting.getVolumes()); for (UserVolumeSettings uvSetting : uvSettings) { String volUID = RecoverPointUtils.getGuidBufferAsString(uvSetting.getVolumeInfo().getRawUids(), false); if (wwnListCopy.contains(volUID.toLowerCase(Locale.ENGLISH))) { // Remove the volUID from the list wwnListCopy.remove(volUID.toLowerCase(Locale.ENGLISH)); // We are getting the index of the first production copy because we only need to // get the cluster ID of the source. All source copies in a CG, across different Rsets, are on the same cluster. // Hence we are ok fetching the first one and getting its cluster id and using it. ConsistencyGroupCopyUID productionCopyUID = cgSetting.getProductionCopiesUIDs().get(0); // Get the RecoverPoint CG name and ID String cgName = cgSetting.getName(); ConsistencyGroupUID cgUID = cgSetting.getGroupUID(); // Get the Copy information RPCopy rpCopy = new RPCopy(); rpCopy.setCGGroupCopyUID(uvSetting.getGroupCopyUID()); Set<RPCopy> copies = new HashSet<RPCopy>(); copies.add(rpCopy); logger.info("Source WWN: " + volUID + " is on RecoverPoint CG " + cgName + " with RecoverPoint CGID " + cgUID.getId()); rpCG = new RPConsistencyGroup(); rpCG.setName(cgName); rpCG.setCGUID(cgUID); rpCG.setClusterUID(productionCopyUID.getGlobalCopyUID().getClusterUID()); rpCG.setSiteToArrayIDsMap(mapCGToStorageArraysNoConnection(cgSetting)); rpCG.setCopies(copies); returnMap.put(volUID, rpCG); break; } } } if (wwnListCopy.isEmpty()) { break; } } for (String wwnMissing : wwnListCopy) { logger.error("Could not map WWN: " + wwnMissing); unmappedWWNs.add(wwnMissing); } return returnMap; } catch (FunctionalAPIActionFailedException_Exception e) { logger.error(e.getMessage()); return null; } catch (FunctionalAPIInternalError_Exception e) { logger.error(e.getMessage()); return null; } } /** * Find the arrays for a CG * * @param groupSettings - A groupSettings object which contains, among other things, the array information for the CG * * @return The site to array mappings for the CG * * @throws RecoverPointException **/ public Map<ClusterUID, Set<String>> mapCGToStorageArraysNoConnection(ConsistencyGroupSettings groupSettings) throws RecoverPointException { Set<String> siteArraySet = null; Map<ClusterUID, Set<String>> returnMap = new HashMap<ClusterUID, Set<String>>(); Set<ClusterUID> siteSet = new HashSet<ClusterUID>(); // First find out the sites involved in the CG ClusterUID ClusterUID = null; boolean foundSite = false; for (ReplicationSetSettings replicationSet : groupSettings.getReplicationSetsSettings()) { for (UserVolumeSettings userVolume : replicationSet.getVolumes()) { ClusterUID = userVolume.getClusterUID(); foundSite = false; for (ClusterUID mappedSite : siteSet) { if (ClusterUID.getId() == mappedSite.getId()) { foundSite = true; break; } } if (!foundSite) { siteSet.add(ClusterUID); } } } for (ClusterUID mappedSite : siteSet) { siteArraySet = new HashSet<String>(); for (ReplicationSetSettings replicationSet1 : groupSettings.getReplicationSetsSettings()) { for (UserVolumeSettings userVolume : replicationSet1.getVolumes()) { ClusterUID = userVolume.getClusterUID(); if (ClusterUID.getId() == mappedSite.getId()) { if (userVolume.getVolumeInfo().getVendorName().equalsIgnoreCase("DGC")) { siteArraySet.add(userVolume.getVolumeInfo().getArraySerialNumber()); } } } } if (!siteArraySet.isEmpty()) { returnMap.put(mappedSite, siteArraySet); } } if (!returnMap.isEmpty()) { return returnMap; } else { return null; } } /** * Create bookmarks for a CG * * @param impl - RP handle to use for RP operations * @param rpCGMap - The mapping of RP CGs to WWNs. Used to create a list of CGs to bookmark * @param request - Information about the bookmark to request * * @return CreateBookmarkResponse - Results of the create bookmark. * TODO: Return bookmark information (date/time) * * @throws RecoverPointException **/ public CreateBookmarkResponse createCGBookmarks(FunctionalAPIImpl impl, Map<String, RPConsistencyGroup> rpCGMap, CreateBookmarkRequestParams request) throws RecoverPointException { Set<ConsistencyGroupUID> uniqueCGUIDSet = new HashSet<ConsistencyGroupUID>(); List<ConsistencyGroupUID> uniqueCGUIDlist = new LinkedList<ConsistencyGroupUID>(); Set<RPConsistencyGroup> rpCGSet = new HashSet<RPConsistencyGroup>(); CreateBookmarkResponse response = new CreateBookmarkResponse(); for (String volume : rpCGMap.keySet()) { RPConsistencyGroup rpCG = rpCGMap.get(volume); if (rpCG.getCGUID() != null) { boolean foundCGUID = false; ConsistencyGroupUID cguid = rpCG.getCGUID(); for (ConsistencyGroupUID cguidunique : uniqueCGUIDSet) { if (cguidunique.getId() == cguid.getId()) { foundCGUID = true; break; } } if (!foundCGUID) { logger.info("Adding CG: " + rpCG.getName() + " with ID " + rpCG.getCGUID().getId() + " to unique CGUID list"); uniqueCGUIDSet.add(cguid); uniqueCGUIDlist.add(cguid); rpCGSet.add(rpCG); } } } // Make sure the CG is in a good state before we make bookmarks RecoverPointImageManagementUtils imageManager = new RecoverPointImageManagementUtils(); for (ConsistencyGroupUID cgID : uniqueCGUIDlist) { // Make sure the CG is ready for enable imageManager.waitForCGLinkState(impl, cgID, RecoverPointImageManagementUtils.getPipeActiveState(impl, cgID), PipeState.PAUSED); } try { impl.createBookmark(uniqueCGUIDlist, request.getBookmark(), BookmarkConsolidationPolicy.NEVER_CONSOLIDATE, SnapshotConsistencyType.APPLICATION_CONSISTENT); logger.info(String.format("Created RP Bookmark successfully: %s", request.getBookmark())); response.setCgBookmarkMap(findRPBookmarks(impl, rpCGSet, request)); response.setReturnCode(RecoverPointReturnCode.SUCCESS); } catch (FunctionalAPIActionFailedException_Exception | FunctionalAPIInternalError_Exception e) { throw RecoverPointException.exceptions.failedToCreateBookmarkOnRecoverPoint(e); } return response; } /** * Find the bookmarks that were created for a CG * * @param impl - RP handle to use for RP operations * @param rpCGset - List of CGs that had this bookmark created * @param request - Information about the bookmark that was created on the CGs * * @return Map of CGs with the bookmark information (CDP and/or CRR) * * @throws RecoverPointException **/ public Map<RPConsistencyGroup, Set<RPBookmark>> findRPBookmarks(FunctionalAPIImpl impl, Set<RPConsistencyGroup> rpCGSet, CreateBookmarkRequestParams request) throws RecoverPointException { Map<RPConsistencyGroup, Set<RPBookmark>> returnMap = new HashMap<RPConsistencyGroup, Set<RPBookmark>>(); final int numRetries = 6; final int secondsToWaitForRetry = 5; RecoverPointCopyType rpCopyType = RecoverPointCopyType.UNKNOWN_PROTECTION; boolean wantCDP = false; boolean wantCRR = false; boolean acceptAnyCopy = false; // If rpCopyType not specified // TODO: acceptAnyCopy will always be set to true, this is because no RP copy type is being specified. This will be taken care of // later. if (rpCopyType == null || rpCopyType == RecoverPointCopyType.UNKNOWN_PROTECTION) { acceptAnyCopy = true; } else { if (rpCopyType == RecoverPointCopyType.CDP_PROTECTION) { wantCDP = true; } else if (rpCopyType == RecoverPointCopyType.CRR_PROTECTION) { wantCRR = true; } else if (rpCopyType == RecoverPointCopyType.CRR_PROTECTION) { wantCRR = true; wantCDP = true; } } boolean tooManyRetries = false; for (RPConsistencyGroup rpCG : rpCGSet) { if (tooManyRetries) { // Stop trying break; } for (int i = 0; i < numRetries; i++) { logger.info(String.format("Getting event markers for CG: %s. Attempt number %d. Copy type: %s", rpCG.getName() != null ? rpCG.getName() : rpCG.getCGUID().getId(), i, rpCopyType.toString())); Set<RPBookmark> rpEventMarkersForCG = getBookmarksForMostRecentBookmarkName(impl, request, rpCG.getCGUID()); if (rpEventMarkersForCG != null) { if (acceptAnyCopy && (!rpEventMarkersForCG.isEmpty())) { // We will take anything, and we found at least one event marker returnMap.put(rpCG, rpEventMarkersForCG); break; // Go to the next CG } else if ((wantCDP && wantCRR) && rpEventMarkersForCG.size() > 1) { // Need 2 event markers for CLR returnMap.put(rpCG, rpEventMarkersForCG); break; // Go to the next CG } else if ((wantCDP && wantCRR) && rpEventMarkersForCG.size() < 2) { logger.error("Didn't find enough bookmarks for CG: " + rpCG.getName() + ". Going to sleep and retry."); } else if (!rpEventMarkersForCG.isEmpty()) { // Either want CDP or CRR and we found at least 1 returnMap.put(rpCG, rpEventMarkersForCG); break; // Go to the next CG } else { logger.error("Didn't find enough bookmarks for CG: " + rpCG.getName() + ". Going to sleep and retry."); } } else { // Didn't get what we wanted logger.error("Didn't find any bookmarks for CG: " + rpCG.getName() + ". Going to sleep and retry."); } try { Thread.sleep(Long.valueOf((secondsToWaitForRetry * numMillisInSecond))); } catch (InterruptedException e) { // NOSONAR // It's ok to ignore this } } } if (returnMap.size() != rpCGSet.size()) { throw RecoverPointException.exceptions.failedToFindExpectedBookmarks(); } return returnMap; } /** * Find the most recent bookmarks that were created for a CG with a given name * * @param impl - RP handle to use for RP operations * @param request - Information about the bookmark that was created on the CGs * @param cgUID - The CG to look for bookmarks * * @return A set of RP bookmarks found on the CG * * @throws RecoverPointException **/ private Set<RPBookmark> getBookmarksForMostRecentBookmarkName(FunctionalAPIImpl impl, CreateBookmarkRequestParams request, ConsistencyGroupUID cgUID) throws RecoverPointException { Set<RPBookmark> returnBookmarkSet = null; try { String bookmarkName = request.getBookmark(); Set<RPBookmark> bookmarkSet = new HashSet<RPBookmark>(); ConsistencyGroupSettings cgSettings = impl.getGroupSettings(cgUID); List<ConsistencyGroupCopySettings> cgCopySettings = cgSettings.getGroupCopiesSettings(); ConsistencyGroupCopyUID prodCopyUID = cgSettings.getLatestSourceCopyUID(); for (ConsistencyGroupCopySettings cgcopysetting : cgCopySettings) { RPBookmark newEM = new RPBookmark(); newEM.setCGGroupCopyUID(cgcopysetting.getCopyUID()); newEM.setBookmarkName(bookmarkName); newEM.setBookmarkTime(null); bookmarkSet.add(newEM); } logger.debug("Getting list of snapshots with event marker name: " + bookmarkName); List<ConsistencyGroupCopySnapshots> cgCopySnapList = impl.getGroupSnapshots(cgUID).getCopiesSnapshots(); for (ConsistencyGroupCopySnapshots cgCopySnap : cgCopySnapList) { ConsistencyGroupCopyUID copyUID = cgCopySnap.getCopyUID(); logger.debug("Found " + cgCopySnap.getSnapshots().size() + " snapshots on copy: " + copyUID.getGlobalCopyUID().getCopyUID()); for (Snapshot snapItem : cgCopySnap.getSnapshots()) { if (snapItem.getDescription().equals(bookmarkName)) { for (RPBookmark rpBookmark : bookmarkSet) { ConsistencyGroupCopyUID rpBookmarkCopyCG = rpBookmark.getCGGroupCopyUID(); if (RecoverPointUtils.copiesEqual(copyUID, rpBookmarkCopyCG)) { // Update record with bookmark time, and add back rpBookmark.setBookmarkTime(snapItem.getClosingTimeStamp()); Timestamp protectionTimeStr = new Timestamp(snapItem.getClosingTimeStamp().getTimeInMicroSeconds() / numMicroSecondsInMilli); // Remove it, and add it back RPBookmark updatedBookmark = new RPBookmark(); updatedBookmark.setBookmarkTime(snapItem.getClosingTimeStamp()); updatedBookmark.setCGGroupCopyUID(rpBookmark.getCGGroupCopyUID()); logger.info("Found our bookmark with time: " + protectionTimeStr.toString() + " and group copy ID: " + rpBookmark.getCGGroupCopyUID().getGlobalCopyUID().getCopyUID()); updatedBookmark.setBookmarkName(rpBookmark.getBookmarkName()); updatedBookmark.setProductionCopyUID(prodCopyUID); if (returnBookmarkSet == null) { returnBookmarkSet = new HashSet<RPBookmark>(); } // TODO: logic is suspect, need to revisit. Why are we removing and adding the same object?? returnBookmarkSet.remove(updatedBookmark); returnBookmarkSet.add(updatedBookmark); } } } } } } catch (FunctionalAPIActionFailedException_Exception e) { throw RecoverPointException.exceptions.exceptionLookingForBookmarks(e); } catch (FunctionalAPIInternalError_Exception e) { throw RecoverPointException.exceptions.exceptionLookingForBookmarks(e); } logger.debug("Return set has " + ((returnBookmarkSet != null) ? returnBookmarkSet.size() : 0) + " items"); return ((returnBookmarkSet != null) ? returnBookmarkSet : null); } /** * Find the bookmarks associated with a consistency group * * @param impl - RP handle to use for RP operations * @param cgUID - The CG to look for bookmarks * * @return A set of RP bookmarks found on the CG * * @throws RecoverPointException **/ public List<RPBookmark> getRPBookmarksForCG(FunctionalAPIImpl impl, ConsistencyGroupUID cgUID) throws RecoverPointException { List<RPBookmark> returnBookmarkSet = null; try { logger.debug("Getting list of snapshots for CG: " + cgUID.getId()); List<ConsistencyGroupCopySnapshots> cgCopySnapList = impl.getGroupSnapshots(cgUID).getCopiesSnapshots(); for (ConsistencyGroupCopySnapshots cgCopySnap : cgCopySnapList) { ConsistencyGroupCopyUID copyUID = cgCopySnap.getCopyUID(); logger.debug("Found " + cgCopySnap.getSnapshots().size() + " snapshots on copy: " + copyUID.getGlobalCopyUID().getCopyUID()); for (Snapshot snapItem : cgCopySnap.getSnapshots()) { // We're not interested in bookmarks without names if (snapItem.getDescription() != null && !snapItem.getDescription().isEmpty()) { RPBookmark bookmark = new RPBookmark(); bookmark.setBookmarkTime(snapItem.getClosingTimeStamp()); bookmark.setCGGroupCopyUID(cgCopySnap.getCopyUID()); bookmark.setBookmarkName(snapItem.getDescription()); if (returnBookmarkSet == null) { returnBookmarkSet = new ArrayList<RPBookmark>(); } returnBookmarkSet.add(bookmark); logger.debug("Recording bookmark: " + bookmark.getBookmarkName()); } } } } catch (FunctionalAPIActionFailedException_Exception e) { throw RecoverPointException.exceptions.exceptionLookingForBookmarks(e); } catch (FunctionalAPIInternalError_Exception e) { throw RecoverPointException.exceptions.exceptionLookingForBookmarks(e); } logger.debug("Return set has " + ((returnBookmarkSet != null) ? returnBookmarkSet.size() : 0) + " items"); return ((returnBookmarkSet != null) ? returnBookmarkSet : null); } }