/* * Copyright (c) 2017 Dell EMC Corporation * All Rights Reserved */ package com.emc.storageos.db.server.upgrade.impl.callback; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.junit.Assert; import org.junit.BeforeClass; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.NamedURI; import com.emc.storageos.db.client.model.ProtectionSet; import com.emc.storageos.db.client.model.StringMap; import com.emc.storageos.db.client.model.StringSet; 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.model.VpoolProtectionVarraySettings; import com.emc.storageos.db.client.upgrade.BaseCustomMigrationCallback; import com.emc.storageos.db.client.upgrade.callbacks.MetroPointVolumeInternalSiteNameMigration; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.server.DbsvcTestBase; import com.emc.storageos.db.server.upgrade.DbSimpleMigrationTestBase; /** * Test that invalid internalSiteName values are overwritten with correct values for * MetroPoint VPlex source volumes. * * This class tests the following migration callback classes: * - MetroPointVolumeInternalSiteNameMigration */ public class MetroPointVolumeInternalSiteNameMigrationTest extends DbSimpleMigrationTestBase { private static final Logger log = LoggerFactory.getLogger(MetroPointVolumeInternalSiteNameMigrationTest.class); private static final String SOURCE_INTERNAL_SITE = "0x690acf1a08ae6e1e"; private static final String STANDBY_INTERNAL_SITE = "0x3e29b09d4f02ef61"; private static final String SOURCE_RP_COPY_NAME = "ACTIVE_PROD_COPY"; private static final String STANDBY_RP_COPY_NAME = "STANDBY_PROD_COPY"; private static URI sourceVirtualArrayURI = URIUtil.createId(VirtualArray.class); private static URI standbyVirtualArrayURI = URIUtil.createId(VirtualArray.class); private static List<URI> validVolumeURIs = new ArrayList<URI>(); private static List<URI> invalidVolumeURIs = new ArrayList<URI>(); private static URI nonMetroPointRPVplexVolumeURI; private static URI nonRPVolumeURI; private static URI nullInternalSiteNamesVolume; private static URI blockConsistencyGroupURI; private static URI protectionSetURI; @SuppressWarnings("serial") @BeforeClass public static void setup() throws IOException { customMigrationCallbacks.put("3.5", new ArrayList<BaseCustomMigrationCallback>() { { add(new MetroPointVolumeInternalSiteNameMigration()); } }); DbsvcTestBase.setup(); } @Override protected void prepareData() throws Exception { blockConsistencyGroupURI = createBlockConsistencyGroup(); protectionSetURI = createProtectionSet(); // Prepare a mixture of "valid" VPlex MetroPoint volumes where 10 have the correct source site name and 10 have the // incorrect source site name. List<Volume> validVolumesValidInternalSite = createRPVolumeData("valid-site-vol", 10, SOURCE_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); List<Volume> validVolumesInvalidInternalSite = createRPVolumeData("invalid-site-vol", 10, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); List<Volume> validVolumes = new ArrayList<Volume>(); validVolumes.addAll(validVolumesValidInternalSite); validVolumes.addAll(validVolumesInvalidInternalSite); for (Volume vol : validVolumes) { createBackingVolumes(vol, 2, sourceVirtualArrayURI, SOURCE_INTERNAL_SITE); _dbClient.createObject(vol); validVolumeURIs.add(vol.getId()); } // Create a VPlex MetroPoint source volume with a null internal site name. Migration should set the // internal site name correctly on the source VPlex volume here. List<Volume> invalidSiteNameVolumes = createRPVolumeData("invalid-inernalsitename", 1, NullColumnValueGetter.getNullStr(), PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); Volume invalidSiteNameVolume = invalidSiteNameVolumes.get(0); createBackingVolumes(invalidSiteNameVolume, 2, sourceVirtualArrayURI, SOURCE_INTERNAL_SITE); _dbClient.createObject(invalidSiteNameVolume); validVolumeURIs.add(invalidSiteNameVolume.getId()); // Create a VPlex MetroPoint source volume with a null RP copy name List<Volume> invalidCopyNameVolumes = createRPVolumeData("invalid-rpcopy", 1, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), NullColumnValueGetter.getNullStr(), sourceVirtualArrayURI, true); Volume invalidCopyNameVolume = invalidCopyNameVolumes.get(0); createBackingVolumes(invalidCopyNameVolume, 2, sourceVirtualArrayURI, SOURCE_INTERNAL_SITE); _dbClient.createObject(invalidCopyNameVolume); invalidVolumeURIs.add(invalidCopyNameVolume.getId()); // Create a VPlex MetroPoint source volume with a null virtual array List<Volume> invalidVirtualArrayVolumes = createRPVolumeData("invalid-varray", 1, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, NullColumnValueGetter.getNullURI(), true); Volume invalidVirtualArrayVolume = invalidVirtualArrayVolumes.get(0); createBackingVolumes(invalidVirtualArrayVolume, 2, sourceVirtualArrayURI, SOURCE_INTERNAL_SITE); _dbClient.createObject(invalidVirtualArrayVolume); invalidVolumeURIs.add(invalidVirtualArrayVolume.getId()); // The volumes created below all have the standby internal site name set on the VPlex MetroPoint source // volume. The volumes are setup with invalid data preventing the migration to properly take place. We // want to verify that the internal site name was unable to be changed. // Create a VPlex MetroPoint source volume with only 1 backing volume (invalid) List<Volume> invalidAssociatedVolumes = createRPVolumeData("invalid-associatedvols", 1, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); Volume invalidAssociatedVolume = invalidAssociatedVolumes.get(0); createBackingVolumes(invalidAssociatedVolume, 1, sourceVirtualArrayURI, SOURCE_INTERNAL_SITE); _dbClient.createObject(invalidAssociatedVolume); invalidVolumeURIs.add(invalidAssociatedVolume.getId()); // Create a VPlex MetroPoint source volume where the source side backing volume has a null virtual array List<Volume> invalidBackingVarrayVolumes = createRPVolumeData("invalid-backing-varray", 1, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); Volume invalidBackingVarrayVolume = invalidBackingVarrayVolumes.get(0); createBackingVolumes(invalidBackingVarrayVolume, 2, NullColumnValueGetter.getNullURI(), SOURCE_INTERNAL_SITE); _dbClient.createObject(invalidBackingVarrayVolume); invalidVolumeURIs.add(invalidBackingVarrayVolume.getId()); // Create a VPlex MetroPoint source volume where the source side backing volume has a null internal site name List<Volume> invalidBackingSiteNameVolumes = createRPVolumeData("invalid-backing-backing-site", 1, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); Volume invalidBackingSiteNameVolume = invalidBackingSiteNameVolumes.get(0); createBackingVolumes(invalidBackingSiteNameVolume, 2, sourceVirtualArrayURI, NullColumnValueGetter.getNullStr()); _dbClient.createObject(invalidBackingSiteNameVolume); invalidVolumeURIs.add(invalidBackingSiteNameVolume.getId()); // Create a VPlex MetroPoint source volume where the parent and source side backing volumes have a null internal site name List<Volume> invalidSourceAndBackingSiteNameVolumes = createRPVolumeData("invalid-source-and-backing-site", 1, NullColumnValueGetter.getNullStr(), PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, true); Volume invalidSourceAndBackingSiteNameVolume = invalidSourceAndBackingSiteNameVolumes.get(0); createBackingVolumes(invalidSourceAndBackingSiteNameVolume, 2, sourceVirtualArrayURI, NullColumnValueGetter.getNullStr()); _dbClient.createObject(invalidSourceAndBackingSiteNameVolume); nullInternalSiteNamesVolume = invalidSourceAndBackingSiteNameVolume.getId(); // Create non-MetroPoint RPVPlex volume List<Volume> rpVplexNonMetroPointVolumes = createRPVolumeData("non-metropoint-rpvplex", 1, STANDBY_INTERNAL_SITE, PersonalityTypes.SOURCE.name(), SOURCE_RP_COPY_NAME, sourceVirtualArrayURI, false); Volume rpVplexNonMetroPointVolume = rpVplexNonMetroPointVolumes.get(0); createBackingVolumes(rpVplexNonMetroPointVolume, 2, sourceVirtualArrayURI, NullColumnValueGetter.getNullStr()); _dbClient.createObject(rpVplexNonMetroPointVolume); nonMetroPointRPVplexVolumeURI = rpVplexNonMetroPointVolume.getId(); // Create non-RP volume List<Volume> nonRpVolumes = createVolumeData("non-rp", 1, sourceVirtualArrayURI); Volume nonRpVolume = nonRpVolumes.get(0); _dbClient.createObject(nonRpVolume); nonRPVolumeURI = nonRpVolume.getId(); } @Override protected void verifyResults() throws Exception { verifyVolumeResults(); } /** * Creates RecoverPoint Volume objects. * * @param name the name of the volume * @param numVolumes the number of volumes to create * @param internalSiteName the internal site name of the volume(s) * @param personality the personality of the volume(s) * @param copyName the RP copy name of the volume(s) * @param virtualArray the virtual array of the volume(s) * @return a List of Volumes */ private List<Volume> createRPVolumeData(String name, int numVolumes, String internalSiteName, String personality, String copyName, URI virtualArray, boolean isMetroPoint) { List<Volume> volumes = new ArrayList<Volume>(); for (int i = 1; i <= numVolumes; i++) { Volume volume = new Volume(); URI volumeURI = URIUtil.createId(Volume.class); String volName = name + i; volume.setId(volumeURI); volume.setLabel(name + i); volume.setPersonality(personality); volume.setRpCopyName(copyName); volume.setVirtualArray(virtualArray); volume.setInternalSiteName(internalSiteName); volume.setVirtualPool(createRPVirtualPool(volName + "target-varray", volName + "-vpool", isMetroPoint)); volume.setConsistencyGroup(blockConsistencyGroupURI); volume.setProtectionSet(new NamedURI(protectionSetURI, name + i)); volumes.add(volume); } return volumes; } private URI createBlockConsistencyGroup() { BlockConsistencyGroup cg = new BlockConsistencyGroup(); URI cgURI = URIUtil.createId(BlockConsistencyGroup.class); cg.setId(cgURI); cg.setLabel("consistencyGroup"); _dbClient.createObject(cg); return cgURI; } private URI createProtectionSet() { ProtectionSet ps = new ProtectionSet(); URI psURI = URIUtil.createId(ProtectionSet.class); ps.setId(psURI); ps.setLabel("protectionSet"); _dbClient.createObject(ps); return psURI; } /** * Creates Volume objects. * * @param name the name of the volume * @param numVolumes the number of volumes to create * @param virtualArray the virtual array of the volume(s) * @return a List of Volumes */ private List<Volume> createVolumeData(String name, int numVolumes, URI virtualArray) { List<Volume> volumes = new ArrayList<Volume>(); for (int i = 1; i <= numVolumes; i++) { Volume volume = new Volume(); URI volumeURI = URIUtil.createId(Volume.class); String volName = name + i; volume.setId(volumeURI); volume.setLabel(name + i); volume.setVirtualArray(virtualArray); volume.setVirtualPool(createVirtualPool(volName + "-vpool")); volumes.add(volume); } return volumes; } /** * Creates backing volumes for the given volume. * * @param volume the parent volume * @param numVolumes the number of backing volumes * @param sourceVirtualArray the source side virtual array * @param sourceInternalSiteName the source side internal site name */ private void createBackingVolumes(Volume volume, int numVolumes, URI sourceVirtualArray, String sourceInternalSiteName) { String volumeName = volume.getLabel(); Volume backingVolume1 = new Volume(); URI backingVolumeURI1 = URIUtil.createId(Volume.class); backingVolume1.setId(backingVolumeURI1); backingVolume1.setLabel(volumeName + "-source"); backingVolume1.setVirtualArray(sourceVirtualArray); backingVolume1.setInternalSiteName(sourceInternalSiteName); backingVolume1.setRpCopyName(SOURCE_RP_COPY_NAME); backingVolume1.setVirtualPool(createVirtualPool(volumeName + "-source-vpool")); _dbClient.createObject(backingVolume1); Volume backingVolume2 = new Volume(); URI backingVolumeURI2 = URIUtil.createId(Volume.class); backingVolume2.setId(backingVolumeURI2); backingVolume2.setLabel(volumeName + "-standby"); backingVolume2.setVirtualArray(standbyVirtualArrayURI); backingVolume2.setInternalSiteName(STANDBY_INTERNAL_SITE); backingVolume2.setRpCopyName(STANDBY_RP_COPY_NAME); backingVolume2.setVirtualPool(createVirtualPool(volumeName + "-standby-vpool")); _dbClient.createObject(backingVolume2); StringSet associatedVols = new StringSet(); associatedVols.add(backingVolumeURI1.toString()); // Only create the standby associated volume if requested to create 2 backing volumes if (numVolumes == 2) { associatedVols.add(backingVolumeURI2.toString()); } volume.setAssociatedVolumes(associatedVols); } private URI createVirtualPool(String vpoolName) { VirtualPool virtualPool = new VirtualPool(); URI virtualPoolURI = URIUtil.createId(VirtualPool.class); virtualPool.setId(virtualPoolURI); virtualPool.setLabel(vpoolName); _dbClient.createObject(virtualPool); return virtualPool.getId(); } private URI createRPVirtualPool(String protectionVarrayName, String vpoolName, boolean metroPoint) { VirtualArray virtualArray = new VirtualArray(); URI virtualArrayURI = URIUtil.createId(VirtualArray.class); virtualArray.setId(virtualArrayURI); virtualArray.setLabel(protectionVarrayName); _dbClient.createObject(virtualArray); VpoolProtectionVarraySettings protectionSettings = new VpoolProtectionVarraySettings(); URI protectionSettingsURI = URIUtil.createId(VpoolProtectionVarraySettings.class); protectionSettings.setId(protectionSettingsURI); protectionSettings.setJournalSize("min"); _dbClient.createObject(protectionSettings); VirtualPool virtualPool = new VirtualPool(); URI virtualPoolURI = URIUtil.createId(VirtualPool.class); virtualPool.setId(virtualPoolURI); virtualPool.setLabel(vpoolName); StringMap protectionVarraySettings = new StringMap(); protectionVarraySettings.put(virtualArrayURI.toString(), protectionSettingsURI.toString()); virtualPool.setProtectionVarraySettings(protectionVarraySettings); if (metroPoint) { virtualPool.setHighAvailability(VirtualPool.HighAvailabilityType.vplex_distributed.name()); virtualPool.setMetroPoint(Boolean.TRUE); } _dbClient.createObject(virtualPool); return virtualPool.getId(); } /** * Verify the migration has successfully completed. * * @throws Exception */ private void verifyVolumeResults() throws Exception { log.info("Verifying migration of Volume.internalSiteName for MetroPoint source volumes."); // get the volumes Iterator<Volume> validVolumeItr = _dbClient.queryIterativeObjects(Volume.class, validVolumeURIs); // Get the block snapshots Iterator<Volume> invalidVolumeItr = _dbClient.queryIterativeObjects(Volume.class, invalidVolumeURIs); while (validVolumeItr.hasNext()) { Volume validVolume = validVolumeItr.next(); Assert.assertTrue( String.format("Volume %s internalSiteName field should be set to %s.", validVolume.getId(), SOURCE_INTERNAL_SITE), SOURCE_INTERNAL_SITE.equals(validVolume.getInternalSiteName())); } while (invalidVolumeItr.hasNext()) { Volume invalidVolume = invalidVolumeItr.next(); Assert.assertTrue(String.format("Volume %s internalSiteName field should not have been changed and should still be set to %s.", invalidVolume.getId(), STANDBY_INTERNAL_SITE), STANDBY_INTERNAL_SITE.equals(invalidVolume.getInternalSiteName())); } if (nullInternalSiteNamesVolume != null) { Volume nullInternalSiteNamesVol = _dbClient.queryObject(Volume.class, nullInternalSiteNamesVolume); Assert.assertTrue(String.format( "Migration for Volume %s should not have happened. The internalSiteName field should still be set to null.", nullInternalSiteNamesVol.getId()), NullColumnValueGetter.isNullValue(nullInternalSiteNamesVol.getInternalSiteName())); } if (nonMetroPointRPVplexVolumeURI != null) { Volume nonMetroPointRPVplexVolume = _dbClient.queryObject(Volume.class, nonMetroPointRPVplexVolumeURI); Assert.assertTrue( String.format( "Volume %s is not a MetroPoint volume and its internalSiteName field should not have been changed and should still be set to %s.", nonMetroPointRPVplexVolume.getId(), STANDBY_INTERNAL_SITE), STANDBY_INTERNAL_SITE.equals(nonMetroPointRPVplexVolume.getInternalSiteName())); } if (nonRPVolumeURI != null) { Volume nonRPVolume = _dbClient.queryObject(Volume.class, nonRPVolumeURI); Assert.assertTrue( String.format( "Volume %s is not a MetroPoint volume and its internalSiteName field should not have been changed and should still be set to %s.", nonRPVolume.getId(), NullColumnValueGetter.getNullStr()), NullColumnValueGetter.isNullValue(nonRPVolume.getInternalSiteName())); } } }