/* * Copyright 2015 EMC Corporation * All Rights Reserved */ /** * Copyright (c) 2008-2015 EMC Corporation * All Rights Reserved * * This software contains the intellectual property of EMC Corporation * or is licensed to EMC Corporation from third parties. Use of this * software and the intellectual property contained therein is expressly * limited to the terms and conditions of the License Agreement under which * it is provided by or on behalf of EMC. */ package com.emc.storageos.api.service.impl.resource.blockingestorchestration; import java.net.URI; import java.util.ArrayList; 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.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.service.impl.resource.blockingestorchestration.context.IngestionRequestContext; import com.emc.storageos.api.service.impl.resource.blockingestorchestration.context.VolumeIngestionContext; import com.emc.storageos.api.service.impl.resource.blockingestorchestration.context.impl.RecoverPointVolumeIngestionContext; import com.emc.storageos.api.service.impl.resource.blockingestorchestration.context.impl.RpVplexVolumeIngestionContext; import com.emc.storageos.api.service.impl.resource.blockingestorchestration.context.impl.VplexVolumeIngestionContext; import com.emc.storageos.api.service.impl.resource.utils.PropertySetterUtil; import com.emc.storageos.api.service.impl.resource.utils.VolumeIngestionUtil; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.model.AbstractChangeTrackingSet; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockMirror; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.BlockSnapshotSession; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.ProtectionSet; import com.emc.storageos.db.client.model.ProtectionSystem; import com.emc.storageos.db.client.model.StoragePort; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.model.VirtualPool; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.Volume.PersonalityTypes; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedExportMask; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedProtectionSet; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedProtectionSet.SupportedCGCharacteristics; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume.SupportedVolumeInformation; import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper; import com.emc.storageos.util.ConnectivityUtil; import com.google.common.base.Joiner; /** * RecoverPoint Ingestion * * Ingestion of RecoverPoint is done one volume at a time, like any other ingestion. * The goal is to allow ingestion to occur in any order: journals, targets, and sources. * BlockConsistencyGroup and ProtectionSet objects are not created until all volumes associated * with the RP CG are ingested. All Volume objects should be flagged to not appear in the UI * during this intermediate phase. (NO_PUBLIC_ACCESS or similar) * * Commmon RP Volume Ingestion Steps: * - A Volume object is created with respective personality field (SOURCE/TARGET/METADATA) filled-in * - The UnManagedVolume reference is removed from the UnManagedProtectionSet * - A ManagedVolume reference is added to the UnManagedProtectionSet * - The export mask that is attached to the ProtectionSystem in the UnManagedProtectionSet that contains this volume is ingested * * Journal Ingestion: * - RP attributes are added to Volume: RP copy name, protection system, etc * * Source Ingestion: * - RP attributes are added to Volume: RP copy name, Replication Set name, protection system, etc. * - Any target volume already ingested (in the unmanaged source's MANAGED_TARGET list) is added to source volume's RP Target list * - Any target volume not yet ingested, remove unmanaged source volume ID from unmanaged target volume * - Any target volume not yet ingested, add managed source volume ID from unmanaged target volume * * Target Ingestion: * - RP attributes are added to Volume: RP copy name, Replication Set name, protection system, etc. * - If source volume is already ingested, add volume to source volume's RP Target list * - If source volume is not yet ingested, remove unmanaged target volume from unmanaged source volume's unmanaged target list * - If source volume is not yet ingested, add managed target volume to managed source volume's managed target list * * The last volume in the UnManagedProtection set to be ingested triggers full RP CG ingestion: * * Criteria for Full Ingestion of an RP CG: * - All Journals, Sources, and Targets associated with the UnManagedProtectionSet are now Managed volumes * - Validation occurs where needed, such as ensuring that the journals and targets are assigned to the right vpools * - BlockConsistencyGroup and ProtectionSet objects are created and all ingested volumes therein are updated with references to them. * */ public class BlockRecoverPointIngestOrchestrator extends BlockIngestOrchestrator { private static final Logger _logger = LoggerFactory.getLogger(BlockRecoverPointIngestOrchestrator.class); private static final String LABEL_NA = "N/A"; @Override protected void checkUnmanagedVolumeReplicas(UnManagedVolume unmanagedVolume) { return; } @Override public <T extends BlockObject> T ingestBlockObjects(IngestionRequestContext parentRequestContext, Class<T> clazz) throws IngestionException { if (parentRequestContext == null) { _logger.error("Parent request context is null. Failing ingestion operation"); throw IngestionException.exceptions.couldNotCreateVolume("Parent request context is null"); } RecoverPointVolumeIngestionContext volumeContext = (RecoverPointVolumeIngestionContext) parentRequestContext.getVolumeContext(); UnManagedVolume unManagedVolume = volumeContext.getUnmanagedVolume(); // Validation checks on the unmanaged volume we're trying to ingest validateUnManagedVolumeProperties(unManagedVolume, volumeContext.getVarray(unManagedVolume), volumeContext.getVpool(unManagedVolume), volumeContext.getProject()); BlockObject blockObject = volumeContext.getManagedBlockObject(); // This ingestion orchestrator only deals with Volume objects. (snapshots, mirrors, clones aren't protected by RP) if (blockObject != null && !(blockObject instanceof Volume)) { _logger.error("Ingesting a non-volume object in RecoverPoint is not allowed: " + blockObject.getId().toString()); throw IngestionException.exceptions.rpIngestingNonVolumeObject(unManagedVolume.getNativeGuid()); } // Make sure there's an unmanaged protection set UnManagedProtectionSet umpset = volumeContext.getUnManagedProtectionSet(); // Make sure there's an unmanaged protection set, and validate it if (umpset == null) { _logger.warn("No unmanaged protection set could be found for unmanaged volume: " + volumeContext.getUnmanagedVolume().getNativeGuid() + " Please run unmanaged CG discovery of registered protection system"); throw IngestionException.exceptions.unManagedProtectionSetNotFound( volumeContext.getUnmanagedVolume().getNativeGuid()); } validateUnmanagedProtectionSet(volumeContext.getVpool(unManagedVolume), unManagedVolume, umpset); // Test ingestion status message _logger.info("Printing Ingestion Report before Ingestion Attempt"); _logger.info(getRPIngestionStatus(volumeContext)); Volume volume = (Volume) blockObject; boolean unManagedVolumeExported = VolumeIngestionUtil.checkUnManagedResourceIsNonRPExported(unManagedVolume) && !unManagedVolume.getUnmanagedExportMasks().isEmpty(); if (isExportIngestionPending(volume, unManagedVolume.getId(), unManagedVolumeExported)) { _logger.info("Volume {} has already been ingested for RecoverPoint, but is still exported via UnManagedExportMasks: {}", volume.forDisplay(), unManagedVolume.getUnmanagedExportMasks()); return clazz.cast(volume); } // Perform RP-specific volume ingestion volume = performRPVolumeIngestion(parentRequestContext, volumeContext, unManagedVolume, volume); // Decorate volume with RP Properties. decorateVolumeWithRPProperties(volumeContext, volume, unManagedVolume); // Update the unmanaged protection set decorateUnManagedProtectionSet(volumeContext, volume, unManagedVolume); // Perform RP-specific export ingestion performRPExportIngestion(parentRequestContext, volumeContext, unManagedVolume, volume); // Print post-ingestion report _logger.info("Printing Ingestion Report After Ingestion"); _logger.info(getRPIngestionStatus(volumeContext)); // If the volume is not exported to host/cluster then check for RP CG fully ingested. // Otherwise check after the export masks are ingested. if (!volumeContext.isVolumeExported()) { // Create the managed protection set/CG objects when we have all of the volumes ingested if (validateAllVolumesInCGIngested(parentRequestContext, volumeContext, unManagedVolume)) { _logger.info("Successfully ingested all volumes associated with RP consistency group"); VolumeIngestionUtil.validateRPVolumesAlignWithIngestVpool(parentRequestContext, umpset, _dbClient); createProtectionSet(volumeContext); BlockConsistencyGroup bcg = createBlockConsistencyGroup(volumeContext); volumeContext.getCGObjectsToCreateMap().put(bcg.getId().toString(), bcg); // Once we have a proper managed consistency group and protection set, we need to // sprinkle those references over the managed volumes. decorateVolumeInformationFinalIngest(volumeContext, unManagedVolume); } else { volume.addInternalFlags(INTERNAL_VOLUME_FLAGS); // Add internal flags } } return clazz.cast(volume); } /** * Perform RP volume ingestion. Typically this involves finding the proper ingestion orchestrator * for the volume type (minus the fact it's RP, which got us to this code in the first place), then * calling block ingest on that orchestrator. * * @param parentRequestContext the IngestionRequestContext for the overriding ingestion process * @param rpVolumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param unManagedVolume unmanaged volume we're ingesting * @param volume resulting ingested volume * @return volume that is ingested */ @SuppressWarnings("unchecked") private Volume performRPVolumeIngestion(IngestionRequestContext parentRequestContext, RecoverPointVolumeIngestionContext rpVolumeContext, UnManagedVolume unManagedVolume, Volume volume) { _logger.info("starting RecoverPoint volume ingestion for UnManagedVolume {}", unManagedVolume.forDisplay()); if (null == volume) { // We need to ingest the volume w/o the context of RP. (So, ingest a VMAX if it's VMAX, VPLEX if it's VPLEX, etc) IngestStrategy ingestStrategy = ingestStrategyFactory.buildIngestStrategy(unManagedVolume, IngestStrategyFactory.DISREGARD_PROTECTION); volume = (Volume) ingestStrategy.ingestBlockObjects(rpVolumeContext, VolumeIngestionUtil.getBlockObjectClass(unManagedVolume)); _logger.info("Ingestion ended for unmanagedvolume {}", unManagedVolume.getNativeGuid()); if (null == volume) { throw IngestionException.exceptions.generalVolumeException( unManagedVolume.getLabel(), "check the logs for more details"); } } else { // blockObject already ingested, now just update internalflags & // RP relationships. Run this logic always when volume NO_PUBLIC_ACCESS if (markUnManagedVolumeInactive(parentRequestContext, volume)) { _logger.info("All the related replicas and parent of unManagedVolume {} have been ingested ", unManagedVolume.getNativeGuid()); unManagedVolume.setInactive(true); // Add this unmanaged volume to the list of objects to be deleted if we succeed to run this whole ingestion. parentRequestContext.getUnManagedVolumesToBeDeleted().add(unManagedVolume); } else { _logger.info( "Not all the parent/replicas of unManagedVolume {} have been ingested , hence marking as internal", unManagedVolume.getNativeGuid()); volume.addInternalFlags(INTERNAL_VOLUME_FLAGS); } } rpVolumeContext.setManagedBlockObject(volume); if (null != _dbClient.queryObject(Volume.class, volume.getId())) { rpVolumeContext.addDataObjectToUpdate(volume, unManagedVolume); } else { rpVolumeContext.addBlockObjectToCreate(volume); } return volume; } /** * Decorates the block objects with RP properties. Also updates the unmanaged volume object with * any references needed for future ingestions of RP volumes. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param volume volume that is the result of the ingest * @param unManagedVolume unmanaged volume with RP properties (VolumeInformation) on it */ private void decorateVolumeWithRPProperties(RecoverPointVolumeIngestionContext volumeContext, Volume volume, UnManagedVolume unManagedVolume) { StringSetMap unManagedVolumeInformation = unManagedVolume.getVolumeInformation(); String type = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_PERSONALITY.toString(), unManagedVolumeInformation); _logger.info("decorating {} volume {} with RecoverPoint properties", type, volume.forDisplay()); if (Volume.PersonalityTypes.SOURCE.toString().equalsIgnoreCase(type)) { decorateUpdatesForRPSource(volumeContext, volume, unManagedVolume); } else if (Volume.PersonalityTypes.TARGET.toString().equalsIgnoreCase(type)) { decorateUpdatesForRPTarget(volumeContext, volume, unManagedVolume); } else if (Volume.PersonalityTypes.METADATA.toString().equalsIgnoreCase(type)) { volume.setPersonality(PersonalityTypes.METADATA.toString()); volume.setAccessState(Volume.VolumeAccessState.NOT_READY.toString()); } // Set the various RP related fields String rpCopyName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_COPY_NAME.toString(), unManagedVolumeInformation); String rpRSetName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_RSET_NAME.toString(), unManagedVolumeInformation); String rpProtectionSystem = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_PROTECTIONSYSTEM.toString(), unManagedVolumeInformation); String rpInternalSiteName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_INTERNAL_SITENAME.toString(), unManagedVolumeInformation); // Only set RSet name for Source and Targets. Journals belong to the RP CG and are not part of any particular RSet if (!Volume.PersonalityTypes.METADATA.toString().equalsIgnoreCase(type)) { volume.setRSetName(rpRSetName); // This comes from UNMANAGED_CG discovery of Protection System } volume.setRpCopyName(rpCopyName); // This comes from UNMANAGED_CG discovery of Protection System volume.setInternalSiteName(rpInternalSiteName); // This comes from UNMANAGED_CG discovery of Protection System volume.setProtectionController(URI.create(rpProtectionSystem)); // This comes from UNMANAGED_CG discovery of Protection System volume.setSyncActive(true); // This defaults to true for an active RP protection } /** * Perform updates of the managed volume and associated unmanaged volumes and protection sets * given an RP source volume getting ingested. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param volume managed volume * @param unManagedVolume unmanaged volume */ private void decorateUpdatesForRPSource(RecoverPointVolumeIngestionContext volumeContext, Volume volume, UnManagedVolume unManagedVolume) { StringSetMap unManagedVolumeInformation = unManagedVolume.getVolumeInformation(); volume.setPersonality(PersonalityTypes.SOURCE.toString()); volume.setAccessState(Volume.VolumeAccessState.READWRITE.toString()); volume.setLinkStatus(Volume.LinkStatus.IN_SYNC.toString()); // For RP+VPLEX Distributed and MetroPoint volumes, we want to set the // internal site and copy names on the backing volumes. This helps when identifying // which Export Groups the volume belongs to on the VPLEX. // // For MetroPoint, the same VPLEX Distributed/Metro volume will be exported to // two VPLEX Export Groups (aka Storage Views). One for each RPA Cluster in the // MetroPoint configuration. boolean isVPlexDistributedVolume = false; if (volumeContext.getVolumeContext() instanceof RpVplexVolumeIngestionContext) { VplexVolumeIngestionContext vplexVolumeContext = ((RpVplexVolumeIngestionContext) volumeContext.getVolumeContext()) .getVplexVolumeIngestionContext(); isVPlexDistributedVolume = vplexVolumeContext.getAssociatedVolumeIds(volume).size() > 1; } if (isVPlexDistributedVolume) { // Get the internal site and copy names String rpInternalSiteName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_INTERNAL_SITENAME.toString(), unManagedVolumeInformation); String rpCopyName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_COPY_NAME.toString(), unManagedVolumeInformation); String rpStandbyInternalSiteName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_STANDBY_INTERNAL_SITENAME.toString(), unManagedVolumeInformation); String rpStandbyCopyName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_STANDBY_COPY_NAME.toString(), unManagedVolumeInformation); // We need the VPLEX ingest context to get the backend volume info VplexVolumeIngestionContext vplexVolumeContext = ((RpVplexVolumeIngestionContext) volumeContext.getVolumeContext()) .getVplexVolumeIngestionContext(); // Match the main VPLEX virtual volume varray to one of its backing volume varrays. // Matching should indicate the volume is the VPLEX Source side. // Non-matching varrays will be the VPLEX HA side. for (String associatedVolumeIdStr : vplexVolumeContext.getAssociatedVolumeIds(volume)) { // Find the associated volumes using the context maps or the db if they are already there Volume associatedVolume = VolumeIngestionUtil.findVolume(_dbClient, vplexVolumeContext.getBlockObjectsToBeCreatedMap(), vplexVolumeContext.getDataObjectsToBeUpdatedMap(), associatedVolumeIdStr); // If we can't get the a handle on the associated volume we'll have to throw an exception if (associatedVolume == null) { _logger.error("Could not find associated volume: " + associatedVolumeIdStr + " in DB. Ingestion failed."); throw IngestionException.exceptions.generalVolumeException(unManagedVolume.getNativeGuid(), "Could not find associated volume: " + associatedVolumeIdStr + ", for VPLEX volume: " + volume.getLabel()); } // Compare the varrays for the associated volume and its VPLEX virtual volume if (associatedVolume.getVirtualArray().equals(volume.getVirtualArray())) { associatedVolume.setInternalSiteName(rpInternalSiteName); associatedVolume.setRpCopyName(rpCopyName); } else { // If this is a RP+VPLEX Distributed volume (not MP) there is the potential that // rpStandbyInternalSiteName and rpStandbyCopyName could be null, which is fine. associatedVolume.setInternalSiteName(rpStandbyInternalSiteName); associatedVolume.setRpCopyName(rpStandbyCopyName); } } } // When we ingest a source volume, we need to properly create the RP Target list for that source, // however it is possible that not all (or any) of the RP targets have been ingested yet. Therefore // we need to do as much as we can: // // 1. Process each managed target volume ID in the unmanaged source volume, add to the managed source volume's RP target list. // 2. Go through each unmanaged RP target volume in the unmanaged source volume (before it goes away), add the managed source volume // ID. // 3. Go through each unmanaged RP target volume in the unmanaged source volume, remove the unmanaged source volume ID. // 1. Process each managed target volume ID in the unmanaged source volume, add to the managed source volume's RP target list. StringSet rpManagedTargetVolumeIdStrs = PropertySetterUtil.extractValuesFromStringSet( SupportedVolumeInformation.RP_MANAGED_TARGET_VOLUMES.toString(), unManagedVolumeInformation); _logger.info("adding managed RecoverPoint targets volumes: " + rpManagedTargetVolumeIdStrs); for (String rpManagedTargetVolumeIdStr : rpManagedTargetVolumeIdStrs) { // Check to make sure the target volume is legit. Volume managedTargetVolume = null; BlockObject bo = volumeContext.getRootIngestionRequestContext().findCreatedBlockObject(URI.create(rpManagedTargetVolumeIdStr)); if (bo != null && bo instanceof Volume) { managedTargetVolume = (Volume) bo; } if (managedTargetVolume == null) { _logger.error("Could not find managed target volume: " + rpManagedTargetVolumeIdStr + " in DB. Ingestion failed."); throw IngestionException.exceptions.noManagedTargetVolumeFound(unManagedVolume.getNativeGuid(), rpManagedTargetVolumeIdStr); } _logger.info("\tadding RecoverPoint target volume {}", managedTargetVolume.forDisplay()); if (volume.getRpTargets() == null) { volume.setRpTargets(new StringSet()); } volume.getRpTargets().add(managedTargetVolume.getId().toString()); } // 2. Go through each unmanaged RP target volume in the unmanaged source volume (before it goes away), add the managed source volume // ID. // 3. Go through each unmanaged RP target volume in the unmanaged source volume, remove the unmanaged source volume ID. StringSet rpUnManagedTargetVolumeIdStrs = PropertySetterUtil.extractValuesFromStringSet( SupportedVolumeInformation.RP_UNMANAGED_TARGET_VOLUMES.toString(), unManagedVolumeInformation); _logger.info("updating unmanaged RecoverPoint targets volumes: " + rpUnManagedTargetVolumeIdStrs); for (String rpUnManagedTargetVolumeIdStr : rpUnManagedTargetVolumeIdStrs) { UnManagedVolume unManagedTargetVolume = _dbClient.queryObject(UnManagedVolume.class, URI.create(rpUnManagedTargetVolumeIdStr)); if (unManagedTargetVolume == null) { _logger.error("Could not find unmanaged target volume: " + rpUnManagedTargetVolumeIdStr + " in DB. Ingestion failed."); throw IngestionException.exceptions.noUnManagedTargetVolumeFound(unManagedVolume.getNativeGuid(), rpUnManagedTargetVolumeIdStr); } // (2) Add the managed source volume ID to this target that hasn't been ingested yet, so when it IS ingested, we know // what RP source it belongs to. StringSet rpManagedSourceVolumeId = new StringSet(); rpManagedSourceVolumeId.add(volume.getId().toString()); unManagedTargetVolume.putVolumeInfo(SupportedVolumeInformation.RP_MANAGED_SOURCE_VOLUME.toString(), rpManagedSourceVolumeId); // (3) Remove the unmanaged source volume ID to this target that is going away as a result of ingestion. // This is for completeness. The ID is going away in the DB, so we don't want any references to it anywhere. StringSet rpUnManagedSourceVolumeId = new StringSet(); unManagedTargetVolume.putVolumeInfo(SupportedVolumeInformation.RP_UNMANAGED_SOURCE_VOLUME.toString(), rpUnManagedSourceVolumeId); volumeContext.addUnmanagedTargetVolumeToUpdate(unManagedTargetVolume); } } /** * Perform updates of the managed volume and associated unmanaged volumes and protection sets * given an RP target volume getting ingested. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param volume managed volume * @param unManagedVolume unmanaged volume */ private void decorateUpdatesForRPTarget(RecoverPointVolumeIngestionContext volumeContext, Volume volume, UnManagedVolume unManagedVolume) { StringSetMap unManagedVolumeInformation = unManagedVolume.getVolumeInformation(); // If the target volume is unexported, check if it is in image access state if (!VolumeIngestionUtil.checkUnManagedResourceIsNonRPExported(unManagedVolume) && VolumeIngestionUtil.isRPUnManagedVolumeInImageAccessState(unManagedVolume)) { String rpAccessState = PropertySetterUtil.extractValueFromStringSet(SupportedVolumeInformation.RP_ACCESS_STATE.toString(), unManagedVolume.getVolumeInformation()); _logger.error("RP target unmanaged volume is not exported and is in image access state: " + rpAccessState); throw IngestionException.exceptions.rpUnManagedTargetVolumeInImageAccessState(unManagedVolume.getNativeGuid(), rpAccessState); } volume.setPersonality(PersonalityTypes.TARGET.toString()); volume.setAccessState(Volume.VolumeAccessState.NOT_READY.toString()); volume.setLinkStatus(Volume.LinkStatus.IN_SYNC.toString()); // Any time a target goes from UnManaged -> Managed, we need to ensure that: // 1. If there is a source managed volume, it gets the managed target volume added to its RP Target List // 2. If there is a source Unmanaged volume, the managed target volume added to its RP_MANAGED_TARGET_VOLUMES list // 3. If there is a source Unmanaged volume, the unmanaged target volume is removed from the RP_UNMANAGED_TARGET_VOLUMES list // // This ensures that we don't lose track of sources and targets, regardless of the order volumes are ingested and unmanaged volumes // are deleted during the ingestion process. // First check to see if there's a managed volume out there with this blockObject's ID in its RP target list. // Add this target volume to the RP source's target list String rpManagedSourceVolume = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_MANAGED_SOURCE_VOLUME.toString(), unManagedVolumeInformation); _logger.info("attempting to link managed RecoverPoint source volume {} to target volume {}", rpManagedSourceVolume, volume.forDisplay()); if (rpManagedSourceVolume != null) { // (1) Add the new managed target volume ID to the source volume's RP target list Volume sourceVolume = null; BlockObject bo = volumeContext.getRootIngestionRequestContext().findCreatedBlockObject(URI.create(rpManagedSourceVolume)); if (bo != null && bo instanceof Volume) { sourceVolume = (Volume) bo; } if (sourceVolume == null) { _logger.error("Could not find managed RP source volume in DB: " + rpManagedSourceVolume); throw IngestionException.exceptions.noManagedSourceVolumeFound(unManagedVolume.getNativeGuid(), rpManagedSourceVolume); } if (sourceVolume.getRpTargets() == null) { sourceVolume.setRpTargets(new StringSet()); } sourceVolume.getRpTargets().add(volume.getId().toString()); volumeContext.addManagedSourceVolumeToUpdate(sourceVolume); } else { _logger.info("There is no ingested RP source volume associated with this target yet: " + volume.getLabel()); String rpUnManagedSourceVolume = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_UNMANAGED_SOURCE_VOLUME.toString(), unManagedVolumeInformation); if (rpUnManagedSourceVolume == null) { _logger.error("There is no uningested RP source volume associated with this target either. This is an error condition: " + volume.getLabel()); throw IngestionException.exceptions.noUnManagedSourceVolumeFound(unManagedVolume.getNativeGuid()); } // (2) Add the managed target to the RP_MANAGED_TARGET_VOLUMES list associated with the unmanaged source volume UnManagedVolume unManagedSourceVolume = _dbClient.queryObject(UnManagedVolume.class, URI.create(rpUnManagedSourceVolume)); if (unManagedSourceVolume == null) { _logger.error("Could not find unmanaged RP source volume in DB: " + rpUnManagedSourceVolume); throw IngestionException.exceptions.noUnManagedSourceVolumeFound2(unManagedVolume.getNativeGuid(), rpUnManagedSourceVolume); } StringSet rpManagedTargetVolumeIdStrs = PropertySetterUtil.extractValuesFromStringSet( SupportedVolumeInformation.RP_MANAGED_TARGET_VOLUMES.toString(), unManagedSourceVolume.getVolumeInformation()); rpManagedTargetVolumeIdStrs.add(volume.getId().toString()); unManagedSourceVolume.putVolumeInfo(SupportedVolumeInformation.RP_MANAGED_TARGET_VOLUMES.toString(), rpManagedTargetVolumeIdStrs); // (3) Remove the unmanaged target from the RP_UNMANAGED_TARGET_VOLUMES list associated with the unmanaged source volume StringSet rpUnManagedTargetVolumeIdStrs = PropertySetterUtil.extractValuesFromStringSet( SupportedVolumeInformation.RP_UNMANAGED_TARGET_VOLUMES.toString(), unManagedSourceVolume.getVolumeInformation()); rpUnManagedTargetVolumeIdStrs.remove(unManagedVolume.getId().toString()); unManagedSourceVolume.putVolumeInfo(SupportedVolumeInformation.RP_UNMANAGED_TARGET_VOLUMES.toString(), rpUnManagedTargetVolumeIdStrs); volumeContext.addUnmanagedSourceVolumeToUpdate(unManagedSourceVolume); } } /** * The unmanaged protection is responsible for keeping track of the managed and unmanaged volumes that * are associated with the RP CG. This method keeps those managed and unmanaged IDs up to date. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param umpset unmanaged protection set to update * @param volume the managed volume * @param unManagedVolume the unmanaged volume */ private void decorateUnManagedProtectionSet(RecoverPointVolumeIngestionContext volumeContext, Volume volume, UnManagedVolume unManagedVolume) { UnManagedProtectionSet umpset = volumeContext.getUnManagedProtectionSet(); // Add the volume to the list of managed volumes we have so far. if (!umpset.getManagedVolumeIds().contains(volume.getId().toString())) { umpset.getManagedVolumeIds().add(volume.getId().toString()); } // Remove the unmanaged volume from the list we have so far since that is going inactive. if (umpset.getUnManagedVolumeIds().contains(unManagedVolume.getId().toString())) { umpset.getUnManagedVolumeIds().remove(unManagedVolume.getId().toString()); } // Set up the unmanaged protection set object to be updated volumeContext.addDataObjectToUpdate(umpset, unManagedVolume); } /** * This method will perform all of the final decorations (attribute setting) on the Volume * object after creating the required BlockConsistencyGroup and ProtectionSet objects. * * Fields such as rpCopyName and rSetName were already filled in when we did the ingest of * the volume itself. In this method, we worry about stitching together all of the object * references within the Volume object so it will act like a native CoprHD-created RP volume. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param unManagedVolume the currently ingesting UnManagedVolume */ private void decorateVolumeInformationFinalIngest(IngestionRequestContext requestContext, UnManagedVolume unManagedVolume) { RecoverPointVolumeIngestionContext volumeContext = (RecoverPointVolumeIngestionContext) requestContext.getVolumeContext(); ProtectionSet pset = volumeContext.getManagedProtectionSet(); BlockConsistencyGroup cg = volumeContext.getManagedBlockConsistencyGroup(); if (pset.getVolumes() == null) { _logger.error("No volumes found in protection set: " + pset.getLabel() + ", cannot process ingestion"); throw IngestionException.exceptions.noVolumesFoundInProtectionSet(pset.getLabel()); } List<Volume> volumes = new ArrayList<Volume>(); for (String volId : pset.getVolumes()) { BlockObject volume = requestContext.getRootIngestionRequestContext().findCreatedBlockObject(URI.create(volId)); if (volume != null && volume instanceof Volume) { volumes.add((Volume) volume); } } // Make sure all of the changed managed block objects get updated volumes.add((Volume) volumeContext.getManagedBlockObject()); Set<DataObject> updatedObjects = new HashSet<DataObject>(); VolumeIngestionUtil.decorateRPVolumesCGInfo(volumes, pset, cg, updatedObjects, _dbClient, requestContext); VolumeIngestionUtil.clearPersistedReplicaFlags(requestContext, volumes, updatedObjects, _dbClient); clearReplicaFlagsInIngestionContext(volumeContext, volumes); for (DataObject volume : updatedObjects) { if (volumeContext.getManagedBlockObject().getId().equals(volume.getId()) && (null == _dbClient.queryObject(Volume.class, volume.getId()))) { // this is the managed block object and it hasn't been saved to the db yet continue; } else { // add all volumes except the newly ingested one to the update list volumeContext.addDataObjectToUpdate(volume, unManagedVolume); } } } /** * Clear the flags of replicas which have been updated during the ingestion process * * @param volumeContext * @param volumes RP volumes */ private void clearReplicaFlagsInIngestionContext(RecoverPointVolumeIngestionContext volumeContext, List<Volume> volumes) { for (Set<DataObject> updatedObjects : volumeContext.getDataObjectsToBeUpdatedMap().values()) { for (DataObject updatedObject : updatedObjects) { if (updatedObject instanceof BlockMirror || updatedObject instanceof BlockSnapshot || updatedObject instanceof BlockSnapshotSession || (updatedObject instanceof Volume && !NullColumnValueGetter.isNullURI( ((Volume) updatedObject).getAssociatedSourceVolume() ))) { _logger.info("Clearing internal volume flag of replica {} of RP volume ", updatedObject.getLabel()); updatedObject.clearInternalFlags(INTERNAL_VOLUME_FLAGS); } } } // We need to look for all snapshots and snapshot session in the contexts related to the rp volumes and its backend volumes and // clear their flags. List<String> rpVolumes = new ArrayList<String>(); for (Volume volume : volumes) { rpVolumes.add(volume.getId().toString()); if (RPHelper.isVPlexVolume(volume, _dbClient) && volumeContext instanceof RpVplexVolumeIngestionContext) { VplexVolumeIngestionContext vplexVolumeContext = ((RpVplexVolumeIngestionContext) volumeContext.getVolumeContext()) .getVplexVolumeIngestionContext(); StringSet associatedVolumes = vplexVolumeContext.getAssociatedVolumeIds(volume); rpVolumes.addAll(associatedVolumes); } } for (VolumeIngestionContext volumeIngestionContext : volumeContext.getRootIngestionRequestContext().getProcessedUnManagedVolumeMap() .values()) { if (volumeIngestionContext instanceof IngestionRequestContext) { for (Set<DataObject> objectsToBeUpdated : ((IngestionRequestContext) volumeIngestionContext).getDataObjectsToBeUpdatedMap() .values()) { for (DataObject o : objectsToBeUpdated) { if (o instanceof BlockSnapshot && rpVolumes.contains(((BlockSnapshot) o).getParent().getURI().toString())) { _logger.info("Clearing internal volume flag of BlockSnapshot {} of RP volume ", o.getLabel()); o.clearInternalFlags(INTERNAL_VOLUME_FLAGS); } else if (o instanceof BlockSnapshotSession && rpVolumes.contains(((BlockSnapshotSession) o).getParent().getURI().toString())) { _logger.info("Clearing internal volume flag of BlockSnapshotSession {} of RP volume ", o.getLabel()); o.clearInternalFlags(INTERNAL_VOLUME_FLAGS); } } } } } } /** * RecoverPoint volumes are expected to have export masks where the volume is exported to * a RecoverPoint site. Therefore every RP volume (sources, targets, journals) will need to * go through this code and have their export mask ingested. Even if the mask has already been * ingested by a previous volume ingestion, this method still needs to update the ExportGroup and * ExportMask objects to reflect the newly ingested volume as part of its management. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @param unManagedVolume unmanaged volume * @param volume managed volume * @return managed volume with export ingested */ private void performRPExportIngestion(IngestionRequestContext parentRequestContext, RecoverPointVolumeIngestionContext volumeContext, UnManagedVolume unManagedVolume, Volume volume) { _logger.info("starting RecoverPoint export ingestion for volume {}", volume.forDisplay()); Project project = volumeContext.getProject(); ProtectionSystem protectionSystem = _dbClient.queryObject(ProtectionSystem.class, volume.getProtectionController()); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, volume.getStorageController()); List<UnManagedExportMask> unManagedRPExportMasks = findUnManagedRPExportMask(protectionSystem, unManagedVolume); if (unManagedRPExportMasks.isEmpty()) { _logger.error("Could not find any unmanaged export masks associated with volume: " + unManagedVolume.getLabel()); throw IngestionException.exceptions.noUnManagedExportMaskFound(unManagedVolume.getNativeGuid()); } // Keep a map for internal site name name and varray Map<String, VirtualArray> internalSiteToVarrayMap = new HashMap<String, VirtualArray>(); internalSiteToVarrayMap.put(volume.getInternalSiteName(), volumeContext.getVarray(unManagedVolume)); // If this is a MetroPoint volume we're going to have multiple ExportMasks/ExportGroups to deal with. // We'll need to query the backend volumes for extra info to populate internalSiteToVarrayMap so // we can properly line up the ExportMasks/ExportGroups. boolean metropoint = RPHelper.isMetroPointVolume(_dbClient, volume); if (metropoint) { // We need the VPLEX ingest context to get the backend volume info VplexVolumeIngestionContext vplexVolumeContext = ((RpVplexVolumeIngestionContext) volumeContext.getVolumeContext()) .getVplexVolumeIngestionContext(); for (String associatedVolumeIdStr : vplexVolumeContext.getAssociatedVolumeIds(volume)) { // Find the associated volumes using the context maps or the db if they are already there Volume associatedVolume = VolumeIngestionUtil.findVolume(_dbClient, vplexVolumeContext.getBlockObjectsToBeCreatedMap(), vplexVolumeContext.getDataObjectsToBeUpdatedMap(), associatedVolumeIdStr); String internalSiteName = associatedVolume.getInternalSiteName(); // If we don't already have an entry for this internal site name, let's add it now. if (!internalSiteToVarrayMap.containsKey(internalSiteName)) { internalSiteToVarrayMap.put(internalSiteName, _dbClient.queryObject(VirtualArray.class, associatedVolume.getVirtualArray())); } } } // Loop on the internalSiteToVarrayMap.entrySet(), unless this is a MetroPoint volume // this will more than likely only loop once. for (Entry<String, VirtualArray> entry : internalSiteToVarrayMap.entrySet()) { String internalSiteName = entry.getKey(); VirtualArray virtualArray = entry.getValue(); UnManagedExportMask em = null; if (metropoint) { // Since we're flagged for MetroPoint we need to determine which ExportMask to use. // We need the MetroPoint volume to be added to BOTH ExportGroups that represent the // two Storage Views on VPLEX for cluster-1 and cluster-2. // So let's use the varray to find the cluster we're looking for on this pass and match // it to the maskingViewParth of the UnManagedExportMask. // This should line things up roughly as: // VPLEX Storage View 1 -> VPLEX Cluster1 + RPA1 // VPLEX Storage View 2 -> VPLEX Cluster2 + RPA2 String vplexCluster = ConnectivityUtil.getVplexClusterForVarray(virtualArray.getId(), storageSystem.getId(), _dbClient); // First try and match based on UnManagedExportMask ports for (UnManagedExportMask exportMask : unManagedRPExportMasks) { for (String portUri : exportMask.getKnownStoragePortUris()) { StoragePort port = _dbClient.queryObject(StoragePort.class, URI.create(portUri)); if (port != null && !port.getInactive()) { String vplexClusterForMask = ConnectivityUtil.getVplexClusterOfPort(port); if (vplexCluster.equals(vplexClusterForMask)) { em = exportMask; break; } } } if (em != null) { break; } } if (em == null) { // Last effort, if we still could not find the correct UnManagedExportMask try looking at // the masking view path. // It really shouldn't come to this, but leaving this code just in case. for (UnManagedExportMask exportMask : unManagedRPExportMasks) { if (exportMask.getMaskingViewPath().contains("cluster-" + vplexCluster)) { em = exportMask; break; } } } } else { em = unManagedRPExportMasks.get(0); } // If the mask for ingested volume is in a mask that contains JOURNAL keyword, make sure the ExportGroup created contains // that internal flag. boolean isJournalExport = false; if (em.getMaskName().toLowerCase().contains(VolumeIngestionUtil.RP_JOURNAL)) { isJournalExport = true; } String exportGroupGeneratedName = RPHelper.generateExportGroupName(protectionSystem, storageSystem, internalSiteName, virtualArray, isJournalExport); ExportGroup exportGroup = VolumeIngestionUtil.verifyExportGroupExists( parentRequestContext, exportGroupGeneratedName, project.getId(), em.getKnownInitiatorUris(), virtualArray.getId(), _dbClient); boolean exportGroupCreated = false; if (null == exportGroup) { exportGroupCreated = true; Integer numPaths = em.getZoningMap().size(); _logger.info("Creating Export Group with label {}", em.getMaskName()); exportGroup = RPHelper.createRPExportGroup(exportGroupGeneratedName, virtualArray, project, numPaths, isJournalExport); } if (null != exportGroup) { // check if the ExportGroup has already been fetched ExportGroup loadedExportGroup = parentRequestContext.findExportGroup( exportGroup.getLabel(), project.getId(), virtualArray.getId(), null, null); if (null != loadedExportGroup) { exportGroup = loadedExportGroup; } } volumeContext.setExportGroup(exportGroup); volumeContext.setExportGroupCreated(exportGroupCreated); volumeContext.getRpExportGroupMap().put(exportGroup, exportGroupCreated); // set RP device initiators to be used as the "host" for export mask ingestion List<Initiator> initiators = new ArrayList<Initiator>(); Iterator<Initiator> initiatorItr = _dbClient.queryIterativeObjects(Initiator.class, URIUtil.toURIList(em.getKnownInitiatorUris())); while (initiatorItr.hasNext()) { initiators.add(initiatorItr.next()); } volumeContext.setDeviceInitiators(initiators); // find the ingest export strategy and call into for this unmanaged export mask IngestExportStrategy ingestStrategy = ingestStrategyFactory.buildIngestExportStrategy(unManagedVolume); volume = ingestStrategy.ingestExportMasks(unManagedVolume, volume, volumeContext); if (null == volume) { // an exception should have been thrown by a lower layer in // ingestion did not succeed, but in case it wasn't, throw one throw IngestionException.exceptions.generalVolumeException( unManagedVolume.getLabel(), "check the logs for more details"); } } } /** * This unmanaged volume may be associated with several export masks. We need to find the export mask * that belongs specifically to the RP protection system supplied. * * Note: There could only be more than one mask that contains both the protection system's initiators AND the volume which * would indicate MetroPoint. In a MetroPoint configuration the VPLEX Distributed Source volume is exported to RP * via two different Storage Views. One per VPLEX cluster to two different RPA clusters. * * @param protectionSystem protection system * @param unManagedVolume unmanaged volume * @return unmanaged export masks that belong to the protection system that contains the unmanaged volume */ private List<UnManagedExportMask> findUnManagedRPExportMask(ProtectionSystem protectionSystem, UnManagedVolume unManagedVolume) { List<UnManagedExportMask> unManagedRPExportMasks = new ArrayList<UnManagedExportMask>(); for (String maskIdStr : unManagedVolume.getUnmanagedExportMasks()) { // Find the mask associated with the protection system. UnManagedExportMask em = _dbClient.queryObject(UnManagedExportMask.class, URI.create(maskIdStr)); if (em == null) { _logger.error("UnManagedExportMask with ID: " + maskIdStr + " could not be found in DB. Could already be ingested."); continue; } // Check for unlikely conditions on the mask, such as no initiators assigned. if (em.getKnownInitiatorNetworkIds() == null || em.getKnownInitiatorNetworkIds().isEmpty()) { _logger.error( "UnManagedExportMask with ID: " + maskIdStr + " does not contain any RP initiators. Ignoring for ingestion."); continue; } boolean foundMask = false; for (String wwn : em.getKnownInitiatorNetworkIds()) { for (Entry<String, AbstractChangeTrackingSet<String>> siteInitEntry : protectionSystem.getSiteInitiators().entrySet()) { if (siteInitEntry.getValue().contains(wwn)) { _logger.info(String .format("UnManagedVolume %s was found in UnManagedExportMask %s and will be ingested (if not ingested already)", unManagedVolume.getLabel(), em.getMaskName())); unManagedRPExportMasks.add(em); foundMask = true; break; } } if (foundMask) { break; } } } return unManagedRPExportMasks; } /** * Check to see if all of the volumes associated with the RP CG are now ingested. * * @param parentRequestContext parent request context object * @param volumeContext ingestion context object * @param unManagedVolume unmanaged volume object * * @return true if all volumes in CG are ingested */ private boolean validateAllVolumesInCGIngested(IngestionRequestContext parentRequestContext, RecoverPointVolumeIngestionContext volumeContext, UnManagedVolume unManagedVolume) { UnManagedProtectionSet umpset = volumeContext.getUnManagedProtectionSet(); if (umpset == null) { _logger.error("Unable to find unmanaged protection set associated with volume: " + unManagedVolume.getId() + " Please run unmanaged CG discovery of registered protection systems"); throw IngestionException.exceptions.unManagedProtectionSetNotFound(unManagedVolume.getNativeGuid()); } return VolumeIngestionUtil.validateAllVolumesInCGIngested(parentRequestContext.findAllUnManagedVolumesToBeDeleted(), umpset, parentRequestContext, _dbClient); } /** * Create the managed protection set associated with the ingested RP volumes. * Also, as a side-effect, insert the protection set ID into each of the impacted volumes. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @return a new protection set object */ private ProtectionSet createProtectionSet(RecoverPointVolumeIngestionContext volumeContext) { UnManagedProtectionSet umpset = volumeContext.getUnManagedProtectionSet(); ProtectionSet pset = VolumeIngestionUtil.findOrCreateProtectionSet( volumeContext, volumeContext.getUnmanagedVolume(), umpset, _dbClient); volumeContext.setManagedProtectionSet(pset); return pset; } /** * Create the block consistency group object associated with the CG as part of ingestion. * * @param volumeContext the RecoverPointVolumeIngestionContext for the volume currently being ingested * @return a new block consistency group object */ private BlockConsistencyGroup createBlockConsistencyGroup(RecoverPointVolumeIngestionContext volumeContext) { ProtectionSet pset = volumeContext.getManagedProtectionSet(); BlockConsistencyGroup cg = VolumeIngestionUtil.findOrCreateRPBlockConsistencyGroup( volumeContext, volumeContext.getUnmanagedVolume(), pset, _dbClient); volumeContext.setManagedBlockConsistencyGroup(cg); return cg; } /** * Validates the UnManagedVolume Properties to make sure it has everything needed to be ingested. * * @param unManagedVolume unmanaged volume * @param virtualArray virtual array * @param virtualPool virtual pool * @param project project */ private void validateUnManagedVolumeProperties(UnManagedVolume unManagedVolume, VirtualArray virtualArray, VirtualPool virtualPool, Project project) { // For example, you could put a check in here that ensures that the TARGET/METADATA are associated // with some RP vpool. It would be good to fail the ingestion early with an error that says "You're // trying to ingest this target/journal volume in a vpool that is not associated with a RP vpool." // First check: Make sure a SOURCE vpool is being ingested with an RP vpool (and not a target/base vpool) String type = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_PERSONALITY.toString(), unManagedVolume.getVolumeInformation()); _logger.info("Type found: " + type); if ((Volume.PersonalityTypes.SOURCE.toString().equalsIgnoreCase(type)) && (virtualPool.getProtectionVarraySettings() == null)) { throw IngestionException.exceptions.invalidSourceRPVirtualPool(unManagedVolume.getLabel(), virtualPool.getLabel()); } if (VolumeIngestionUtil.checkUnManagedVolumeHasReplicas(unManagedVolume)) { // check if the RP protected volume has any mirrors. If yes, throw an error as we don't support this configuration in ViPR as of // now StringSet mirrors = PropertySetterUtil.extractValuesFromStringSet(SupportedVolumeInformation.MIRRORS.toString(), unManagedVolume.getVolumeInformation()); if (mirrors != null && !mirrors.isEmpty()) { String mirrorsString = Joiner.on(", ").join(mirrors); _logger.info("Unmanaged RP volume {} has mirrors: {} associated which is not supported", unManagedVolume.getLabel(), mirrorsString); throw IngestionException.exceptions.rpUnManagedVolumeCannotHaveMirrors(unManagedVolume.getLabel(), mirrorsString); } // If the RP volume has snaps, check if the vpool allows snaps. StringSet snapshots = PropertySetterUtil.extractValuesFromStringSet(SupportedVolumeInformation.SNAPSHOTS.toString(), unManagedVolume.getVolumeInformation()); if (snapshots != null && !snapshots.isEmpty()) { int numOfSnaps = snapshots.size(); if (VirtualPool.vPoolSpecifiesSnapshots(virtualPool)) { if (numOfSnaps > virtualPool.getMaxNativeSnapshots()) { String reason = "volume has more snapshots (" + numOfSnaps + ") than vpool allows"; _logger.error(reason); throw IngestionException.exceptions.validationException(reason); } } else { String reason = "vpool does not allow snapshots, but volume has " + numOfSnaps + " snapshot(s)"; _logger.error(reason); throw IngestionException.exceptions.validationException(reason); } } } } /** * Validate the unmanaged protection set before ingesting the volume. Is the CG healthy? Does the protection set's * policies match the vpool (in the case of RP source volumes) * * @param vpool virtual pool * @param unManagedVolume unmanaged volume attempted to be ingested * @param umpset unmanaged protection set with settings/state information */ private void validateUnmanagedProtectionSet(VirtualPool vpool, UnManagedVolume unManagedVolume, UnManagedProtectionSet umpset) { if (umpset == null) { _logger.warn("No unmanaged protection set could be found for unmanaged volume: " + unManagedVolume.getNativeGuid() + " Please run unmanaged CG discovery of registered protection system"); throw IngestionException.exceptions.unManagedProtectionSetNotFound(unManagedVolume.getNativeGuid()); } // Check the health of the consistency group first. This applies to any volume associated with an RP CG. String rpHealthy = umpset.getCGCharacteristics() .get(SupportedCGCharacteristics.IS_HEALTHY.toString()); if (!Boolean.valueOf(rpHealthy.toUpperCase())) { _logger.error(String.format("At the time of discovery, the RecoverPoint consistency group %s associated " + "with unmanaged volume %s was in an unhealthy state (disabled, paused, or in error). If the issue " + "has been resolved, rerun discovery of unmanaged consistency groups for this protection system.", umpset.getCgName(), unManagedVolume.getNativeGuid())); throw IngestionException.exceptions.unManagedProtectionSetNotHealthy(umpset.getCgName(), unManagedVolume.getNativeGuid()); } // Specifically for RP source volumes: Make sure the sync/async of the vpool aligns with the protection set. String personality = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_PERSONALITY.toString(), unManagedVolume.getVolumeInformation()); if (personality == null) { _logger.error("Could not find the personality of unmanaged volume " + unManagedVolume.getLabel() + ". Run unmanaged consistency group discovery for this protection system."); throw IngestionException.exceptions.rpObjectNotSet("Personality", unManagedVolume.getId()); } if (Volume.PersonalityTypes.SOURCE.toString().equalsIgnoreCase(personality)) { String rpSync = umpset.getCGCharacteristics() .get(SupportedCGCharacteristics.IS_SYNC.toString()); // rpCopyMode is allowed to be blank, and blank defaults to ASYNC on the RP appliance. String rpCopyMode = (vpool.getRpCopyMode() != null) ? vpool.getRpCopyMode() : VirtualPool.RPCopyMode.ASYNCHRONOUS.toString(); if (Boolean.valueOf(rpSync.toUpperCase()) && rpCopyMode.equalsIgnoreCase(VirtualPool.RPCopyMode.ASYNCHRONOUS.toString())) { _logger.error(String.format("The RecoverPoint consistency group %s associated with unmanaged volume %s is " + "running in synchronous mode, but the virtual pool requires asynchronous mode. Modify virtual pool settings " + "or create a new virtual pool, rerun unmanaged consistency group discovery, and then rerun ingestion.", umpset.getCgName(), unManagedVolume.getNativeGuid())); throw IngestionException.exceptions.unManagedProtectionSetNotAsync(umpset.getCgName(), unManagedVolume.getNativeGuid()); } if (!Boolean.valueOf(rpSync.toUpperCase()) && rpCopyMode.equalsIgnoreCase(VirtualPool.RPCopyMode.SYNCHRONOUS.toString())) { _logger.error(String.format("The RecoverPoint consistency group %s associated with unmanaged volume %s is " + "running in asynchronous mode, but the virtual pool requires synchronous mode. Modify virtual pool " + "settings or create a new virtual pool, rerun unmanaged consistency group discovery, and then rerun ingestion.", umpset.getCgName(), unManagedVolume.getNativeGuid())); throw IngestionException.exceptions.unManagedProtectionSetNotSync(umpset.getCgName(), unManagedVolume.getNativeGuid()); } } } private enum ColumnEnum { NAME(0), ID(1), PERSONALITY(2), COPY_NAME(3), RSET_NAME(4), VARRAY(5), VPOOL(6); private final int column; private static Map<Integer, ColumnEnum> map = new HashMap<Integer, ColumnEnum>(); static { for (ColumnEnum columnEnum : ColumnEnum.values()) { map.put(columnEnum.column, columnEnum); } } private ColumnEnum(final int column) { this.column = column; } public static ColumnEnum valueOf(int column) { return map.get(column); } public int getColumnNum() { return column; } } /** * This method will assemble a status printout of the ingestion progress for this protection set. * * @param volumeContext context information * * @return String status (multi-line, formatted) */ private String getRPIngestionStatus(RecoverPointVolumeIngestionContext volumeContext) { StringBuffer sb = new StringBuffer(); UnManagedProtectionSet umpset = volumeContext.getUnManagedProtectionSet(); sb.append("\nRecoverPoint Ingestion Progress Report\n"); sb.append("--------------------------------------\n"); sb.append("RP CG Name: " + umpset.getCgName() + "\n"); if (umpset.getProtectionSystemUri() != null) { ProtectionSystem ps = _dbClient.queryObject(ProtectionSystem.class, umpset.getProtectionSystemUri()); sb.append("Protection System: " + ps.getLabel() + " [" + ps.getIpAddress() + "]"); } // Keep track of the column widths Map<Integer, Integer> columnWidthMap = new HashMap<Integer, Integer>(); for (int column = 0; column < ColumnEnum.map.keySet().size(); column++) { columnWidthMap.put(column, ColumnEnum.valueOf(column).toString().length()); } if (!umpset.getManagedVolumeIds().isEmpty()) { List<Volume> volumes = new ArrayList<Volume>(); sb.append("\n\nIngested Volumes:\n"); for (URI volumeId : URIUtil.toURIList(umpset.getManagedVolumeIds())) { Volume volume = null; BlockObject bo = volumeContext.getRootIngestionRequestContext().findCreatedBlockObject(volumeId); if (bo != null && bo instanceof Volume) { volume = (Volume) bo; } if (volume != null) { volumes.add(volume); } else { continue; } // Get the width of the columns so we can have a compact, formatted printout. // Name, ID, Personality, copy name, rset name, varray name, vpool name if (volume.getLabel() != null && columnWidthMap.get(ColumnEnum.NAME.getColumnNum()) < volume.getLabel().length()) { columnWidthMap.put(ColumnEnum.NAME.getColumnNum(), volume.getLabel().length()); } if (volume.getId() != null && columnWidthMap.get(ColumnEnum.ID.getColumnNum()) < volume.getId().toString().length()) { columnWidthMap.put(ColumnEnum.ID.getColumnNum(), volume.getId().toString().length()); } if (volume.getPersonality() != null && columnWidthMap.get(ColumnEnum.PERSONALITY.getColumnNum()) < volume.getPersonality().length()) { columnWidthMap.put(ColumnEnum.PERSONALITY.getColumnNum(), volume.getPersonality().length()); } if (volume.getRpCopyName() != null && columnWidthMap.get(ColumnEnum.COPY_NAME.getColumnNum()) < volume.getRpCopyName().length()) { columnWidthMap.put(ColumnEnum.COPY_NAME.getColumnNum(), volume.getRpCopyName().length()); } if (volume.getRSetName() != null && columnWidthMap.get(ColumnEnum.RSET_NAME.getColumnNum()) < volume.getRSetName().length()) { columnWidthMap.put(ColumnEnum.RSET_NAME.getColumnNum(), volume.getRSetName().length()); } if (volume.getVirtualArray() != null) { VirtualArray vArray = _dbClient.queryObject(VirtualArray.class, volume.getVirtualArray()); if (vArray != null && vArray.getLabel() != null && columnWidthMap.get(ColumnEnum.RSET_NAME.getColumnNum()) < vArray.getLabel().length()) { columnWidthMap.put(ColumnEnum.VARRAY.getColumnNum(), vArray.getLabel().length()); } } if (volume.getVirtualPool() != null) { VirtualPool vPool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool()); if (vPool != null && vPool.getLabel() != null && columnWidthMap.get(ColumnEnum.RSET_NAME.getColumnNum()) < vPool.getLabel().length()) { columnWidthMap.put(ColumnEnum.VPOOL.getColumnNum(), vPool.getLabel().length()); } } } StringBuffer widthFormat = new StringBuffer(); for (int column = 0; column < ColumnEnum.map.keySet().size(); column++) { StringBuffer formatBuf = new StringBuffer(); formatBuf.append("%"); formatBuf.append(String.format("%d", columnWidthMap.get(column))); formatBuf.append("s "); sb.append(String.format(formatBuf.toString(), ColumnEnum.valueOf(column).name())); widthFormat.append(formatBuf.toString()); } sb.append("\n"); widthFormat.append("\n"); // Now actually print the values for (Volume volume : volumes) { String vArrayLabel = LABEL_NA; String vPoolLabel = LABEL_NA; if (volume.getVirtualArray() != null) { VirtualArray vArray = _dbClient.queryObject(VirtualArray.class, volume.getVirtualArray()); vArrayLabel = vArray.getLabel(); } if (volume.getVirtualPool() != null) { VirtualPool vPool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool()); vPoolLabel = vPool.getLabel(); } sb.append(String.format(widthFormat.toString(), volume.getLabel() != null ? volume.getLabel() : LABEL_NA, volume.getId() != null ? volume.getId() : LABEL_NA, volume.getPersonality() != null ? volume.getPersonality() : LABEL_NA, volume.getRpCopyName() != null ? volume.getRpCopyName() : LABEL_NA, volume.getRSetName() != null ? volume.getRSetName() : LABEL_NA, vArrayLabel, vPoolLabel)); } } // Keep track of the column widths columnWidthMap.clear(); for (int column = 0; column < ColumnEnum.map.keySet().size(); column++) { columnWidthMap.put(column, ColumnEnum.valueOf(column).toString().length()); } if (!umpset.getUnManagedVolumeIds().isEmpty()) { sb.append("\nUningested Volumes:\n"); List<UnManagedVolume> volumes = _dbClient.queryObject(UnManagedVolume.class, URIUtil.toURIList(umpset.getUnManagedVolumeIds())); for (UnManagedVolume volume : volumes) { // Get the width of the columns so we can have a compact, formatted printout. // Name, ID, Personality, copy name, rset name, varray name, vpool name if (volume.getLabel() != null && columnWidthMap.get(ColumnEnum.NAME.getColumnNum()) < volume.getLabel().length()) { columnWidthMap.put(ColumnEnum.NAME.getColumnNum(), volume.getLabel().length()); } if (volume.getId() != null && columnWidthMap.get(ColumnEnum.ID.getColumnNum()) < volume.getId().toString().length()) { columnWidthMap.put(ColumnEnum.ID.getColumnNum(), volume.getId().toString().length()); } String personality = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_PERSONALITY.toString(), volume.getVolumeInformation()); if (personality != null && columnWidthMap.get(ColumnEnum.PERSONALITY.getColumnNum()) < personality.length()) { columnWidthMap.put(ColumnEnum.PERSONALITY.getColumnNum(), personality.length()); } String rpCopyName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_COPY_NAME.toString(), volume.getVolumeInformation()); if (rpCopyName != null && columnWidthMap.get(ColumnEnum.COPY_NAME.getColumnNum()) < rpCopyName.length()) { columnWidthMap.put(ColumnEnum.COPY_NAME.getColumnNum(), rpCopyName.length()); } String rsetName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_RSET_NAME.toString(), volume.getVolumeInformation()); if (rsetName != null && columnWidthMap.get(ColumnEnum.RSET_NAME.getColumnNum()) < rsetName.length()) { columnWidthMap.put(ColumnEnum.RSET_NAME.getColumnNum(), rsetName.length()); } columnWidthMap.put(ColumnEnum.VARRAY.getColumnNum(), ColumnEnum.VARRAY.name().length()); columnWidthMap.put(ColumnEnum.VPOOL.getColumnNum(), ColumnEnum.VPOOL.name().length()); } StringBuffer widthFormat = new StringBuffer(); for (int column = 0; column < ColumnEnum.map.keySet().size(); column++) { StringBuffer formatBuf = new StringBuffer(); formatBuf.append("%"); formatBuf.append(String.format("%d", columnWidthMap.get(column))); formatBuf.append("s "); sb.append(String.format(formatBuf.toString(), ColumnEnum.valueOf(column).name())); widthFormat.append(formatBuf.toString()); } sb.append("\n"); widthFormat.append("\n"); // Now actually print the values for (UnManagedVolume volume : volumes) { String vArrayLabel = LABEL_NA; String vPoolLabel = LABEL_NA; String personality = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_PERSONALITY.toString(), volume.getVolumeInformation()); String rpCopyName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_COPY_NAME.toString(), volume.getVolumeInformation()); String rsetName = PropertySetterUtil.extractValueFromStringSet( SupportedVolumeInformation.RP_RSET_NAME.toString(), volume.getVolumeInformation()); sb.append(String.format(widthFormat.toString(), volume.getLabel() != null ? volume.getLabel() : LABEL_NA, volume.getId() != null ? volume.getId() : LABEL_NA, personality != null ? personality : LABEL_NA, rpCopyName != null ? rpCopyName : LABEL_NA, rsetName != null ? rsetName : LABEL_NA, vArrayLabel, vPoolLabel)); } } return sb.toString(); } @Override protected void validateAutoTierPolicy(String autoTierPolicyId, UnManagedVolume unManagedVolume, VirtualPool vPool) { super.validateAutoTierPolicy(autoTierPolicyId, unManagedVolume, vPool); } }