/* * Copyright (c) 2013 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.HashMap; 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.BlockConsistencyGroup.Types; 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.NamedURI; 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.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.TenantOrg; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils; import com.emc.storageos.db.client.upgrade.BaseCustomMigrationCallback; import com.emc.storageos.db.client.upgrade.callbacks.BlockObjectMultipleConsistencyGroupsMigration; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.server.upgrade.DbSimpleMigrationTestBase; /** * Test proper population of the new BlockConsistencyGroup fields and collapsing * multiple BlockConsistencyGroup references to a single references in BlockObject. * * Here's the basic execution flow for the test case: * - setup() runs, bringing up a "pre-migration" version * of the database, using the DbSchemaScannerInterceptor * you supply to hide your new field or column family * when generating the "before" schema. * - Your implementation of prepareData() is called, allowing * you to use the internal _dbClient reference to create any * needed pre-migration test data. * - The database is then shutdown and restarted (without using * the interceptor this time), so the full "after" schema * is available. * - The dbsvc detects the diffs in the schema and executes the * migration callbacks as part of the startup process. * - Your implementation of verifyResults() is called to * allow you to confirm that the migration of your prepared * data went as expected. * * This class tests the following migration callback classes: * - BlockObjectMultipleConsistencyGroupsMigration * * **************************************************************************** * * NOTE: to run this multiple time, delete the folder "dbtest" under dbsvc * * **************************************************************************** * */ public class BlockObjectMultipleConsistencyGroupsMigrationTest extends DbSimpleMigrationTestBase { private static final Logger log = LoggerFactory.getLogger(BlockObjectMultipleConsistencyGroupsMigrationTest.class); // Empty CG private static volatile URI emptyCgURI = null; // Used for RP+VPlex result verification private static volatile URI rpVplexPrimaryConsistencyGroupURI = null; private static HashMap<URI, URI> rpVplexVolumeToCgMapping = new HashMap<URI, URI>(); // Used for VPlex result verification private static volatile URI vplexConsistencyGroupURI = null; private static List<URI> vplexVolumeURIs = new ArrayList<URI>(); // Used for RP result verification private static volatile URI rpConsistencyGroupURI = null; private static volatile URI rpConsistencyGroupURI2 = null; private static volatile URI rpConsistencyGroupURI3 = null; private static List<URI> rpVolumeURIs = new ArrayList<URI>(); // Used for Local Array result verification private static volatile URI localArrayConsistencyGroupURI = null; private static List<URI> blockVolumeURIs = new ArrayList<URI>(); // Used for BlockSnapshot result verification private static List<URI> blockSnapshotURIs = new ArrayList<URI>(); // Used for BlockMirror result verification private static List<URI> blockMirrorURIs = new ArrayList<URI>(); private static String RP_SRC_JOURNAL_APPEND = "-journal-prod"; private static String RP_TGT_JOURNAL_APPEND = "-target-journal-"; private static String RP_TGT_APPEND = "-target-"; private URI projectURI = null; private URI protectionSystemURI = null; private URI staleProtectionSetURI = null; private URI staleProtectionSetURI2 = null; private StringSet staleProtectionSetVolumeURIs = new StringSet(); @BeforeClass public static void setup() throws IOException { customMigrationCallbacks.put("2.1", new ArrayList<BaseCustomMigrationCallback>() { { add(new BlockObjectMultipleConsistencyGroupsMigration()); } }); } @Override protected String getSourceVersion() { return "2.1"; } @Override protected String getTargetVersion() { return "2.2"; } @Override protected void prepareData() throws Exception { prepareProjectData(); createBareBlockConsistencyGroup("emptyCg"); prepareProtectionSystemData(); prepareRPVplexConsistencyGroupData(); prepareRPConsistencyGroupData(); prepareVPlexConsistencyGroupData(); prepareLocalArrayConsistencyGroupData(); prepareBlockSnapshotData("blockSnapshot", 10); prepareBlockMirrorData("blockMirror", 10); prepareProtectionSetWithAllStaleVolumes(); prepareRPConsistencyGroupDataWithDuplicates(); prepareRPConsistencyGroupDataWithStaleVolumes(); } @Override protected void verifyResults() throws Exception { verifyRpVplexConsistencyGroupMigration(); verifyVplexConsistencyGroupMigration(); verifyRpConsistencyGroupMigration(); verifyLocalArrayConsistencyGroupMigration(); verifyBlockSnapshotMigration(); verifyBlockMirrorMigration(); verifyEmptyConsistencyGroupMigration(); verifyStaleProtectionSet(); verifyProtectionSetWith2StaleVolumes(); verifyRpConsistencyGroupWithDuplicatesMigration(); } /** * Prepares and persists the Project/Tenant data. * * @throws Exception */ private void prepareProjectData() throws Exception { TenantOrg tenantOrg = new TenantOrg(); URI tenantOrgURI = URIUtil.createId(TenantOrg.class); tenantOrg.setId(tenantOrgURI); _dbClient.createObject(tenantOrg); Project proj = new Project(); projectURI = URIUtil.createId(Project.class); String projectLabel = "project"; proj.setId(projectURI); proj.setLabel(projectLabel); proj.setTenantOrg(new NamedURI(tenantOrgURI, projectLabel)); _dbClient.createObject(proj); } /** * Prepares the ProtectionSystem data. * * @throws Exception */ private void prepareProtectionSystemData() throws Exception { ProtectionSystem protectionSystem = new ProtectionSystem(); protectionSystemURI = URIUtil.createId(ProtectionSystem.class); protectionSystem.setId(protectionSystemURI); _dbClient.createObject(protectionSystem); } /** * Prepares the RP + VPlex consistency group test data. * * @throws Exception */ private void prepareRPVplexConsistencyGroupData() throws Exception { String cg1Name = "rpVplexCg"; // Create the primary RecoverPoint BlockConsistencyGroup that will be shared by all the // RP+VPlex volumes. BlockConsistencyGroup rpVplexCg = createBlockConsistencyGroup(cg1Name, null, Types.RP.name(), true); // Save the CG references for migration verification. rpVplexPrimaryConsistencyGroupURI = rpVplexCg.getId(); // Create the ProtectionSet that the RP + VPlex volumes will belong to. ProtectionSet rpVplexProtectionSet = createProtectionSet(cg1Name, projectURI); // Create all the RP+VPlex volumes List<Volume> rpVplexVolumes = createRpVolumes("rpVplexCgVolume1", 1, rpVplexProtectionSet, true); rpVplexVolumes.addAll(createRpVolumes("rpVplexCgVolume2", 1, rpVplexProtectionSet, true)); // Add the RP + VPlex volumes to the RP consistency group addVolumesToBlockConsistencyGroup(rpVplexCg.getId(), rpVplexVolumes); // Add the RP+VPlex volumes to the protection set. addVolumesToProtectionSet(rpVplexProtectionSet.getId(), rpVplexVolumes); } /** * Prepares a ProtectionSet with volume references to volumes that do not * exist in the DB. * * @throws Exception */ private void prepareProtectionSetWithAllStaleVolumes() throws Exception { String cgName = "staleVolumeTest"; ProtectionSet protectionSet = createProtectionSet(cgName, projectURI); staleProtectionSetURI = protectionSet.getId(); URI volumeURI1 = URIUtil.createId(Volume.class); URI volumeURI2 = URIUtil.createId(Volume.class); URI volumeURI3 = URIUtil.createId(Volume.class); StringSet volumes = new StringSet(); volumes.add(volumeURI1.toString()); volumes.add(volumeURI2.toString()); volumes.add(volumeURI3.toString()); protectionSet.setVolumes(volumes); _dbClient.persistObject(protectionSet); } /** * Prepare the RecoverPoint only volumes and associated consistency group data. * * @throws Exception */ private void prepareRPConsistencyGroupData() throws Exception { String cg2Name = "rpCg"; // Create the RecoverPoint BlockConsistencyGroup that will be shared by all the // RP volumes. BlockConsistencyGroup rpCg = createBlockConsistencyGroup(cg2Name, null, Types.RP.name(), true); // Save the CG references for migration verification. rpConsistencyGroupURI = rpCg.getId(); // create a protection set and volumes and add them to the CG addProtectionSetAndVolumes(rpCg, "rpCg", 0); } /** * Prepare the RecoverPoint only volumes and associated consistency group data. * * @throws Exception */ private void prepareRPConsistencyGroupDataWithDuplicates() throws Exception { String cg2Name = "rpCg3"; // Create the RecoverPoint BlockConsistencyGroup that will be shared by all the // RP volumes. BlockConsistencyGroup rpCg = createBlockConsistencyGroup(cg2Name, null, Types.RP.name(), true); // Save the CG references for migration verification. rpConsistencyGroupURI3 = rpCg.getId(); // create a protection set and volumes and add them to the CG addProtectionSetAndVolumes(rpCg, "ps-dup", 3); // create a protection set and volumes and add them to the CG addProtectionSetAndVolumes(rpCg, "ps-dup", 3); // same volume in two protection sets // Create the ProtectionSet that the RP volumes will belong to. String prefix = "ps-dup2-"; ProtectionSet cg2ps1 = createProtectionSet(prefix + "ProtectionSet", projectURI); ProtectionSet cg2ps2 = createProtectionSet(prefix + "ProtectionSet", projectURI); // Create all the RP volumes List<Volume> rpCgVolumes = createRpVolumes(prefix + "VolumeA", 1, cg2ps1, false); // Add the RP volumes to the RP consistency group addVolumesToBlockConsistencyGroup(rpCg.getId(), rpCgVolumes); // Add the RP volumes to the protection set addVolumesToProtectionSet(cg2ps1.getId(), rpCgVolumes); addVolumesToProtectionSet(cg2ps2.getId(), rpCgVolumes); } /** * creates snapshot objects * * @param numSnapshots * @param volume * @param name snapshot name */ public void addSnapshots(int numSnapshots, Volume volume, BlockConsistencyGroup cg, ProtectionSet ps, String name) { for (int i = 1; i <= numSnapshots; i++) { BlockSnapshot blockSnapshot = new BlockSnapshot(); URI blockSnapshotURI = URIUtil.createId(BlockSnapshot.class); blockSnapshotURIs.add(blockSnapshotURI); blockSnapshot.setId(blockSnapshotURI); blockSnapshot.setLabel(name + i); blockSnapshot.setSnapsetLabel(name + i); blockSnapshot.setParent(new NamedURI(volume.getId(), name + i)); blockSnapshot.addConsistencyGroup(cg.getId().toString()); blockSnapshot.setProtectionSet(ps.getId()); _dbClient.createObject(blockSnapshot); } } /** * adds new protection set with volumes to a CG; it's intentional that the protection set name is * not unique * * @param rpCg * @param prefix * @throws Exception */ private void addProtectionSetAndVolumes(BlockConsistencyGroup rpCg, String prefix, int numberOfSnaps) throws Exception { // Create the ProtectionSet that the RP volumes will belong to. ProtectionSet cg2ps = createProtectionSet(prefix + "ProtectionSet", projectURI); // Create all the RP volumes List<Volume> rpCgVolumes = createRpVolumes(prefix + "VolumeA", 2, cg2ps, false); rpCgVolumes.addAll(createRpVolumes(prefix + "VolumeB", 2, cg2ps, false)); // Add the RP volumes to the RP consistency group addVolumesToBlockConsistencyGroup(rpCg.getId(), rpCgVolumes); // Add the RP volumes to the protection set addVolumesToProtectionSet(cg2ps.getId(), rpCgVolumes); // add some snapshots addSnapshots(numberOfSnaps, rpCgVolumes.iterator().next(), rpCg, cg2ps, prefix + "SnapVolmeA"); } /** * Prepare the RecoverPoint only volumes and associated consistency group data. * * @throws Exception */ private void prepareRPConsistencyGroupDataWithStaleVolumes() throws Exception { String cg2Name = "rpCg2"; // Create the RecoverPoint BlockConsistencyGroup that will be shared by all the // RP volumes. BlockConsistencyGroup rpCg = createBlockConsistencyGroup(cg2Name, null, Types.RP.name(), true); // Save the CG references for migration verification. rpConsistencyGroupURI2 = rpCg.getId(); // Create the ProtectionSet that the RP volumes will belong to. ProtectionSet cg2ps = createProtectionSet("rpCg2ProtectionSet", projectURI); // Create all the RP volumes List<Volume> rpCg2Volumes = createRpVolumes("rpCg2VolumeA", 2, cg2ps, false); rpCg2Volumes.addAll(createRpVolumes("rpCg2VolumeB", 2, cg2ps, false)); // Add the RP volumes to the RP consistency group addVolumesToBlockConsistencyGroup(rpCg.getId(), rpCg2Volumes); // Add the RP volumes to the protection set addVolumesToProtectionSet(cg2ps.getId(), rpCg2Volumes); // Add stale volume references URI staleVolumeURI1 = URIUtil.createId(Volume.class); URI staleVolumeURI2 = URIUtil.createId(Volume.class); ProtectionSet ps = _dbClient.queryObject(ProtectionSet.class, cg2ps.getId()); StringSet vols = ps.getVolumes(); vols.add(staleVolumeURI1.toString()); vols.add(staleVolumeURI2.toString()); staleProtectionSetURI2 = cg2ps.getId(); staleProtectionSetVolumeURIs.add(staleVolumeURI1.toString()); staleProtectionSetVolumeURIs.add(staleVolumeURI2.toString()); _dbClient.persistObject(ps); } /** * Prepare the VPlex volumes and associated consistency group data. * * @throws Exception */ private void prepareVPlexConsistencyGroupData() throws Exception { // Create a VPlex storage system StorageSystem storageSystem = createStorageSystem(true); // Create the VPlex volumes and add them to the VPlex consistency group List<Volume> vplexVolumes = createVPlexVolumes("vplexVolume", 3, storageSystem.getId()); // Prior to 2.2, VPlex only CGs (nothing to do with RP) did not set the CG type of VPLEX. So we pass false here. BlockConsistencyGroup vplexCg = createBlockConsistencyGroup("vplexCg", storageSystem.getId(), Types.VPLEX.name(), false); // Save a references to the cg for migration verification vplexConsistencyGroupURI = vplexCg.getId(); // Add the VPlex volumes to the VPlex consistency group addVolumesToBlockConsistencyGroup(vplexCg.getId(), vplexVolumes); } /** * Create block volumes and associated local array consistency group. * * @throws Exception */ private void prepareLocalArrayConsistencyGroupData() throws Exception { // Create a non-VPlex storage system StorageSystem storageSystem = createStorageSystem(false); // Create the block volumes that will be part of the cg List<Volume> blockVolumes = createBlockVolumes("blockVolume", 3, storageSystem.getId()); // Create the consistency group and add the block volumes BlockConsistencyGroup localArrayCg = createBlockConsistencyGroup("localArrayCg", storageSystem.getId(), Types.LOCAL.name(), true); localArrayConsistencyGroupURI = localArrayCg.getId(); addVolumesToBlockConsistencyGroup(localArrayCg.getId(), blockVolumes); } /** * Creates the BlockObject BlockSnapshot data. * * @param name * @param numSnapshots * @throws Exception */ private void prepareBlockSnapshotData(String name, int numSnapshots) throws Exception { // Create the volume for the snapshots Volume volume = new Volume(); URI volumeURI = URIUtil.createId(Volume.class); StorageSystem storageSystem = createStorageSystem(false); volume.setId(volumeURI); volume.setStorageController(storageSystem.getId()); String volName = "blockSnapshotVolume"; volume.setLabel(volName); BlockConsistencyGroup cg = createBlockConsistencyGroup("blockSnapshotConsistencyGroup", storageSystem.getId(), Types.LOCAL.name(), true); volume.setConsistencyGroup(cg.getId()); _dbClient.createObject(volume); for (int i = 1; i <= numSnapshots; i++) { BlockSnapshot blockSnapshot = new BlockSnapshot(); URI blockSnapshotURI = URIUtil.createId(BlockSnapshot.class); blockSnapshotURIs.add(blockSnapshotURI); blockSnapshot.setId(blockSnapshotURI); blockSnapshot.setLabel(name + i); blockSnapshot.setSnapsetLabel(name + i); blockSnapshot.setParent(new NamedURI(volume.getId(), name + i)); blockSnapshot.addConsistencyGroup(cg.getId().toString()); _dbClient.createObject(blockSnapshot); } } /** * Creates the BlockObject BlockMirror data. * * @param name * @param numBlockMirrors * @throws Exception */ private void prepareBlockMirrorData(String name, int numBlockMirrors) throws Exception { BlockConsistencyGroup cg = createBlockConsistencyGroup("blockMirrorConsistencyGroup", null, Types.LOCAL.name(), true); for (int i = 1; i <= numBlockMirrors; i++) { BlockMirror blockMirror = new BlockMirror(); URI blockMirrorURI = URIUtil.createId(BlockMirror.class); blockMirrorURIs.add(blockMirrorURI); blockMirror.setId(blockMirrorURI); blockMirror.setLabel(name + i); // Set the 'old' field value so it can be migrated blockMirror.addConsistencyGroup(cg.getId().toString()); _dbClient.createObject(blockMirror); } } /** * Convenience method to create a ProtectionSet. * * @param cgName * @param projectURI * @return * @throws Exception */ private ProtectionSet createProtectionSet(String cgName, URI projectURI) throws Exception { ProtectionSet protectionSet = new ProtectionSet(); URI protectionSetURI = URIUtil.createId(ProtectionSet.class); protectionSet.setId(protectionSetURI); protectionSet.setLabel("ViPR-" + cgName); protectionSet.setProtectionId("790520997"); protectionSet.setProtectionStatus("ENABLED"); protectionSet.setProject(projectURI); protectionSet.setProtectionSystem(protectionSystemURI); _dbClient.createObject(protectionSet); return protectionSet; } /** * Convenience method that creates a StorageSystem. * * @param isVplex true if a VPlex storage system is to be created, false otherwise. * @return */ private StorageSystem createStorageSystem(boolean isVplex) { StorageSystem ss = new StorageSystem(); URI storageSystemId = URIUtil.createId(StorageSystem.class); ss.setId(storageSystemId); if (isVplex) { ss.setLabel("VPLEX+FNM00114300288:FNM00114600001"); ss.setNativeGuid("VPLEX+FNM00114300288:FNM00114600001"); ss.setSystemType("vplex"); } else { ss.setLabel("SYMMETRIX+000195701573"); ss.setNativeGuid("SYMMETRIX+000195701573"); ss.setSystemType("vmax"); } _dbClient.createObject(ss); return ss; } /** * Creates the consistency group used by the BlockObjects. * * @param name * @return */ private BlockConsistencyGroup createBareBlockConsistencyGroup(String name) { BlockConsistencyGroup cg = new BlockConsistencyGroup(); URI cgURI = URIUtil.createId(BlockConsistencyGroup.class); emptyCgURI = cgURI; cg.setId(cgURI); cg.setLabel(name); _dbClient.createObject(cg); return cg; } /** * Creates the consistency group used by the BlockObjects. * * @param name * @return */ private BlockConsistencyGroup createBlockConsistencyGroup(String name, URI storageSystem, String type, boolean setType) { BlockConsistencyGroup cg = new BlockConsistencyGroup(); URI cgURI = URIUtil.createId(BlockConsistencyGroup.class); cg.setId(cgURI); cg.setLabel(name); cg.setStorageController(storageSystem); if (type.equals(Types.LOCAL.name())) { // Set the 'old' field value so it can be migrated cg.setDeviceName("localArrayDeviceName"); } else if (type.equals(Types.VPLEX.name())) { // Set the 'old' field value so it can be migrated cg.setDeviceName("vplexDeviceName"); } else if (type.equals(Types.RP.name())) { // Set the 'old' field value so it can be migrated. cg.setDeviceName("rpDeviceName"); } else if (type.equals(Types.SRDF.name())) { // Set the 'old' field value so it can be migrated cg.setDeviceName("srdfDeviceName"); } if (setType) { cg.setType(type); } // Set the 'old' field value so it can be migrated _dbClient.createObject(cg); return cg; } /** * Convenience method that adds volumes to a protection set. * * @param protectionSetURI * @param volumes */ private void addVolumesToProtectionSet(URI protectionSetURI, List<Volume> volumes) { ProtectionSet protectionSet = _dbClient.queryObject(ProtectionSet.class, protectionSetURI); StringSet vols = new StringSet(); for (Volume volume : volumes) { vols.add(volume.getId().toString()); } protectionSet.setVolumes(vols); _dbClient.persistObject(protectionSet); } /** * Convenience method that creates the BlockObject Volume data. * * @param volumeName * @param numTargets */ private List<Volume> createBlockVolumes(String volumeName, int numVolumes, URI storageSystem) { List<Volume> volumes = new ArrayList<Volume>(); for (int i = 1; i <= numVolumes; i++) { Volume volume = new Volume(); URI volumeURI = URIUtil.createId(Volume.class); blockVolumeURIs.add(volumeURI); volume.setId(volumeURI); volume.setLabel(volumeName + i); volume.setStorageController(storageSystem); _dbClient.createObject(volume); volumes.add(volume); } return volumes; } /** * Convenience method that creates VPlex volumes. * * @param name * @param numberOfVols * @param storageSystem * @return */ private List<Volume> createVPlexVolumes(String name, int numberOfVols, URI storageSystem) { List<Volume> volumes = new ArrayList<Volume>(); for (int i = 1; i <= numberOfVols; i++) { Volume vplexVolume = new Volume(); URI vplexVolumeUri = URIUtil.createId(Volume.class); vplexVolumeURIs.add(vplexVolumeUri); vplexVolume.setId(vplexVolumeUri); vplexVolume.setLabel(name + i); vplexVolume.setNativeId("/clusters/cluster-1/virtual-volumes/device_V000195701573-01E7F_vol" + i); vplexVolume.setStorageController(storageSystem); StringSet associatedVolumes = new StringSet(); associatedVolumes.add("associatedVol1"); vplexVolume.setAssociatedVolumes(associatedVolumes); volumes.add(vplexVolume); } return volumes; } /** * Creates the RP source volume/journal and the specified number of * target/journal volumes. * * @param volumeName * @param numTargets */ private List<Volume> createRpVolumes(String volumeName, int numTargets, ProtectionSet protectionSet, boolean isRpVPlex) { List<Volume> volumes = new ArrayList<Volume>(); StringSet associatedVolumes = new StringSet(); associatedVolumes.add("associatedVol1"); StorageSystem storageSystem = null; if (isRpVPlex) { storageSystem = createStorageSystem(true); } else { storageSystem = createStorageSystem(false); } String rsetName = "RSet-" + volumeName; Volume sourceVolume = new Volume(); URI sourceVolumeURI = URIUtil.createId(Volume.class); volumes.add(sourceVolume); sourceVolume.setId(sourceVolumeURI); sourceVolume.setLabel(volumeName); sourceVolume.setPersonality(Volume.PersonalityTypes.SOURCE.toString()); sourceVolume.setRSetName(rsetName); sourceVolume.setProtectionSet(new NamedURI(protectionSet.getId(), protectionSet.getLabel())); sourceVolume.setStorageController(storageSystem.getId()); if (isRpVPlex) { sourceVolume.setAssociatedVolumes(associatedVolumes); sourceVolume.setNativeId("/clusters/cluster-1/virtual-volumes/device_V000195701573-01E7A_vol"); // Create a VPLEX ViPR BlockConsistencyGroup for the source volume BlockConsistencyGroup sourceVolumeCg = createBlockConsistencyGroup(sourceVolume.getLabel() + "-CG", storageSystem.getId(), Types.VPLEX.name(), true); addVolumeToBlockConsistencyGroup(sourceVolumeCg.getId(), sourceVolume); rpVplexVolumeToCgMapping.put(sourceVolumeURI, sourceVolumeCg.getId()); } else { rpVolumeURIs.add(sourceVolumeURI); } _dbClient.createObject(sourceVolume); Volume sourceVolumeJournal = new Volume(); URI sourceVolumeJournalURI = URIUtil.createId(Volume.class); volumes.add(sourceVolumeJournal); sourceVolumeJournal.setId(sourceVolumeJournalURI); sourceVolumeJournal.setLabel(volumeName + RP_SRC_JOURNAL_APPEND); sourceVolumeJournal.setPersonality(Volume.PersonalityTypes.METADATA.toString()); sourceVolumeJournal.setProtectionSet(new NamedURI(protectionSet.getId(), protectionSet.getLabel())); sourceVolumeJournal.setStorageController(storageSystem.getId()); if (isRpVPlex) { sourceVolumeJournal.setAssociatedVolumes(associatedVolumes); sourceVolumeJournal.setNativeId("/clusters/cluster-1/virtual-volumes/device_V000195701573-01E7B_vol"); // Create a VPLEX ViPR BlockConsistencyGroup for the source journal volume BlockConsistencyGroup sourceVolumeJournalCg = createBlockConsistencyGroup(sourceVolumeJournal.getLabel() + "-CG", storageSystem.getId(), Types.VPLEX.name(), true); addVolumeToBlockConsistencyGroup(sourceVolumeJournalCg.getId(), sourceVolumeJournal); rpVplexVolumeToCgMapping.put(sourceVolumeJournalURI, sourceVolumeJournalCg.getId()); } else { rpVolumeURIs.add(sourceVolumeJournalURI); } _dbClient.createObject(sourceVolumeJournal); for (int i = 1; i <= numTargets; i++) { Volume sourceVolumeTarget = new Volume(); URI sourceVolumeTargetURI = URIUtil.createId(Volume.class); volumes.add(sourceVolumeTarget); sourceVolumeTarget.setId(sourceVolumeTargetURI); sourceVolumeTarget.setLabel(volumeName + RP_TGT_APPEND + "vArray" + i); sourceVolumeTarget.setPersonality(Volume.PersonalityTypes.TARGET.toString()); sourceVolumeTarget.setRSetName(rsetName); sourceVolumeTarget.setProtectionSet(new NamedURI(protectionSet.getId(), protectionSet.getLabel())); sourceVolumeTarget.setStorageController(storageSystem.getId()); if (isRpVPlex) { sourceVolumeTarget.setAssociatedVolumes(associatedVolumes); sourceVolumeTarget.setNativeId("/clusters/cluster-2/virtual-volumes/device_V000195701573-01E7C_vol" + i); // Create a VPLEX ViPR BlockConsistencyGroup for the target volume BlockConsistencyGroup sourceVolumeTargetCg = createBlockConsistencyGroup(sourceVolumeTarget.getLabel() + "-CG", storageSystem.getId(), Types.VPLEX.name(), true); addVolumeToBlockConsistencyGroup(sourceVolumeTargetCg.getId(), sourceVolumeTarget); rpVplexVolumeToCgMapping.put(sourceVolumeTargetURI, sourceVolumeTargetCg.getId()); } else { rpVolumeURIs.add(sourceVolumeTargetURI); } _dbClient.createObject(sourceVolumeTarget); Volume sourceVolumeTargetJournal = new Volume(); URI sourceVolumeTargetJournalURI = URIUtil.createId(Volume.class); volumes.add(sourceVolumeTargetJournal); sourceVolumeTargetJournal.setId(sourceVolumeTargetJournalURI); sourceVolumeTargetJournal.setLabel(volumeName + RP_TGT_JOURNAL_APPEND + "vArray" + i); sourceVolumeTargetJournal.setPersonality(Volume.PersonalityTypes.METADATA.toString()); sourceVolumeTargetJournal.setProtectionSet(new NamedURI(protectionSet.getId(), protectionSet.getLabel())); sourceVolumeTargetJournal.setStorageController(storageSystem.getId()); if (isRpVPlex) { sourceVolumeTargetJournal.setAssociatedVolumes(associatedVolumes); sourceVolumeTargetJournal.setNativeId("/clusters/cluster-2/virtual-volumes/device_V000195701573-01ED_vol" + i); // Create a VPLEX ViPR BlockConsistencyGroup for the source target journal volume BlockConsistencyGroup sourceVolumeTargetJournalCg = createBlockConsistencyGroup(sourceVolumeTargetJournal.getLabel() + "-CG", storageSystem.getId(), Types.VPLEX.name(), true); addVolumeToBlockConsistencyGroup(sourceVolumeTargetJournalCg.getId(), sourceVolumeTargetJournal); rpVplexVolumeToCgMapping.put(sourceVolumeTargetJournalURI, sourceVolumeTargetJournalCg.getId()); } else { rpVolumeURIs.add(sourceVolumeTargetJournalURI); } _dbClient.createObject(sourceVolumeTargetJournal); } return volumes; } /** * Associates a list of volumes with a given BlockConsistencyGroup URI. * * @param cgUri * @param volumes */ private void addVolumesToBlockConsistencyGroup(URI cgUri, List<Volume> volumes) { for (Volume volume : volumes) { addVolumeToBlockConsistencyGroup(cgUri, volume); } } /** * Associates a volume with a given BlockConsistencyGroup URI. * * @param cgUri * @param volume */ private void addVolumeToBlockConsistencyGroup(URI cgUri, Volume volume) { // Set the 'old' field value so it can be migrated volume.addConsistencyGroup(cgUri.toString()); _dbClient.persistObject(volume); } private void verifyEmptyConsistencyGroupMigration() throws Exception { log.info("Verifying empty/unused BlockConsistencyGroup."); BlockConsistencyGroup emptyCg = _dbClient.queryObject(BlockConsistencyGroup.class, emptyCgURI); // Verify that the primary CG now has the VPlex type added to its types list is null Assert.assertTrue("The empty BlockConsistencyGroup.type field should be null.", emptyCg.getType().equals(NullColumnValueGetter.getNullStr())); Assert.assertTrue("The BlockConsistencyGroup.types field should be null.", emptyCg.getTypes().isEmpty()); } /** * Verify the RP+VPlex consistency group and its volumes have been properly migrated. * * @throws Exception */ private void verifyRpVplexConsistencyGroupMigration() throws Exception { log.info("Verifying RP+VPlex BlockConsistencyGroup and associated volume migration."); List<BlockObject> blockObjects = new ArrayList<BlockObject>(); BlockConsistencyGroup rpVplexPrimaryCg = _dbClient.queryObject(BlockConsistencyGroup.class, rpVplexPrimaryConsistencyGroupURI); // Verify the RP+VPLEX consistency group was properly migrated verifyConsistencyGroupMigration(rpVplexPrimaryCg, Types.RP.name(), Types.VPLEX.name()); Assert.assertNotNull("The RP+VPlex BlockConsistencyGroup.systemConsistencyGroups field should be populated.", rpVplexPrimaryCg.getSystemConsistencyGroups()); Assert.assertNotNull("The RP+VPlex BlockConsistencyGroup.systemConsistencyGroups field should contain an entry for " + protectionSystemURI.toString(), rpVplexPrimaryCg.getSystemConsistencyGroups().get(protectionSystemURI.toString())); Assert.assertTrue( "The RP+VPlex BlockConsistencyGroup.systemConsistencyGroups field should contain a mapping for " + protectionSystemURI.toString() + "-> ViPR-" + rpVplexPrimaryCg.getLabel(), rpVplexPrimaryCg.getSystemConsistencyGroups().get(protectionSystemURI.toString()) .contains("ViPR-" + rpVplexPrimaryCg.getLabel())); // Verify that primary CG has a mapping reference for each of the VPlex storage system/cg name. for (URI rpVplexVolumeId : rpVplexVolumeToCgMapping.keySet()) { Volume rpVplexVolume = _dbClient.queryObject(Volume.class, rpVplexVolumeId); blockObjects.add(rpVplexVolume); // Get the VPlex consistency group URI cgUri = rpVplexVolumeToCgMapping.get(rpVplexVolumeId); BlockConsistencyGroup vplexCg = _dbClient.queryObject(BlockConsistencyGroup.class, cgUri); String cgName = vplexCg.getLabel(); String clusterName = getVPlexClusterFromVolume(rpVplexVolume); String storageSystem = rpVplexVolume.getStorageController().toString(); String clusterCgName = BlockConsistencyGroupUtils.buildClusterCgName(clusterName, cgName); // Verify that primary CG contains the correct mapping Assert.assertTrue("The RP+VPlex BlockConsistencyGroup.systemConsistencyGroups field should contain a mapping for " + storageSystem + "->" + clusterCgName, rpVplexPrimaryCg.getSystemConsistencyGroups().get(storageSystem).contains(clusterCgName)); // Verify that the VPlex CG has been marked for deletion Assert.assertTrue("The VPlex BlockConsistencyGroup " + vplexCg.getLabel() + "should be inactive.", vplexCg.getInactive()); } // Verify the volume migration took place correctly verifyBlockObjects(blockObjects); } /** * Verify the VPlex consistency group and its volumes have been properly migrated. * * @throws Exception */ private void verifyVplexConsistencyGroupMigration() throws Exception { log.info("Verifying VPlex BlockConsistencyGroup and associated volume migration."); BlockConsistencyGroup vplexCg = _dbClient.queryObject(BlockConsistencyGroup.class, vplexConsistencyGroupURI); Iterator<Volume> vplexVolumeItr = _dbClient.queryIterativeObjects(Volume.class, vplexVolumeURIs); // Verify the VPLEX consistency group was properly migrated verifyConsistencyGroupMigration(vplexCg, Types.VPLEX.name()); while (vplexVolumeItr.hasNext()) { Volume vplexVolume = vplexVolumeItr.next(); // Get the VPlex consistency group String cgName = vplexCg.getLabel(); String clusterName = getVPlexClusterFromVolume(vplexVolume); String storageSystem = vplexVolume.getStorageController().toString(); String clusterCgName = BlockConsistencyGroupUtils.buildClusterCgName(clusterName, cgName); // Verify that primary CG contains the correct mapping Assert.assertNotNull("The VPlex BlockConsistencyGroup.vplexStorageSystemToCg field should be populated.", vplexCg.getSystemConsistencyGroups()); Assert.assertTrue("The VPlex BlockConsistencyGroup.vplexStorageSystemToCg should contain a key for storage system " + storageSystem, vplexCg.getSystemConsistencyGroups().containsKey(storageSystem)); Assert.assertTrue("The VPlex BlockConsistencyGroup.vplexStorageSystemToCg field should contain a mapping for " + storageSystem + "->" + clusterCgName, vplexCg.getSystemConsistencyGroups().get(storageSystem).contains(clusterCgName)); } // Verify the volume migration took place correctly List<BlockObject> blockObjects = new ArrayList<BlockObject>(); while (vplexVolumeItr.hasNext()) { blockObjects.add(vplexVolumeItr.next()); } verifyBlockObjects(blockObjects); } /** * Verify the RP consistency group and its volumes have been properly migrated. * * @throws Exception */ private void verifyRpConsistencyGroupMigration() throws Exception { log.info("Verifying RP BlockConsistencyGroup and associated volume migration."); BlockConsistencyGroup rpCg = _dbClient.queryObject(BlockConsistencyGroup.class, rpConsistencyGroupURI); Assert.assertNotNull("The RP+VPlex BlockConsistencyGroup.systemConsistencyGroups field should be populated.", rpCg.getSystemConsistencyGroups()); Assert.assertTrue("The RP+VPlex BlockConsistencyGroup.systemConsistencyGroups field should contain a mapping for " + protectionSystemURI.toString() + "-> ViPR-" + rpCg.getLabel(), rpCg.getSystemConsistencyGroups().get(protectionSystemURI.toString()).contains("ViPR-" + rpCg.getLabel())); Iterator<Volume> rpVolumeItr = _dbClient.queryIterativeObjects(Volume.class, rpVolumeURIs); // Verify the RP consistency group was properly migrated verifyConsistencyGroupMigration(rpCg, Types.RP.name()); // Verify the volume migration took place correctly List<BlockObject> blockObjects = new ArrayList<BlockObject>(); while (rpVolumeItr.hasNext()) { blockObjects.add(rpVolumeItr.next()); } verifyBlockObjects(blockObjects); } /** * Verify the RP consistency group and its volumes have been properly migrated. * * @throws Exception */ private void verifyRpConsistencyGroupWithDuplicatesMigration() throws Exception { log.info("Verifying duplicate protection sets were cleaned up."); List<URI> protSetIds = _dbClient.queryByType(ProtectionSet.class, true); List<String> protSetNames = new ArrayList<String>(); for (URI id : protSetIds) { ProtectionSet protSet = _dbClient.queryObject(ProtectionSet.class, id); if (protSet == null || protSet.getInactive()) { continue; } if (protSetNames.contains(protSet.getLabel())) { // fail duplicate protection set Assert.fail("Duplicate protection sets exist after migration " + protSet.getLabel()); } else { protSetNames.add(protSet.getLabel()); // verify that there are no duplicates or stale volumes on the volume list List<String> volumeIds = new ArrayList<String>(); for (String volId : protSet.getVolumes()) { Volume vol = _dbClient.queryObject(Volume.class, URI.create(volId)); if (vol == null || vol.getInactive()) { // fail stale volume on protection set Assert.fail(String.format("Stale volume %s exists on protection set %s exist after migration", volId, protSet.getLabel())); } else if (volumeIds.contains(volId)) { // fail duplicate volume on protection set Assert.fail(String.format("Duplicate volume %s exists on protection set %s exist after migration", volId, protSet.getLabel())); } else if (vol.getProtectionSet() == null || vol.getProtectionSet().getURI() == null || !vol.getProtectionSet().getURI().equals(protSet.getId())) { // fail volume does not point back to protection set Assert.fail(String.format("Volume %s does not point back to protection set %s exist after migration", volId, protSet.getLabel())); } else { volumeIds.add(volId); } } } } // make sure there are no dangling protection sets on volumes List<URI> volumes = _dbClient.queryByType(Volume.class, true); for (URI id : volumes) { Volume vol = _dbClient.queryObject(Volume.class, id); if (vol.getProtectionSet() != null && vol.getProtectionSet().getURI() != null) { ProtectionSet protSet = _dbClient.queryObject(ProtectionSet.class, vol.getProtectionSet().getURI()); if (protSet == null || protSet.getInactive()) { // fail dangling protection set Assert.fail(String.format("Stale protection set %s on volume %s exists after migration", vol.getProtectionSet() .getURI(), id)); } } } // make sure there are no dangling protection sets on snapshots List<URI> snapshots = _dbClient.queryByType(BlockSnapshot.class, true); for (URI id : snapshots) { BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, id); if (snapshot.getProtectionSet() != null) { ProtectionSet protSet = _dbClient.queryObject(ProtectionSet.class, snapshot.getProtectionSet()); if (protSet == null || protSet.getInactive()) { // fail dangling protection set Assert.fail(String.format("Stale protection set %s on snapshot %s exists after migration", snapshot.getProtectionSet(), id)); } } } } /** * Verify the local array consistency group and its volumes have been properly * migrated. * * @throws Exception */ private void verifyLocalArrayConsistencyGroupMigration() throws Exception { log.info("Verifying local array BlockConsistencyGroup and associated volume migration."); BlockConsistencyGroup localArrayCg = _dbClient.queryObject(BlockConsistencyGroup.class, localArrayConsistencyGroupURI); Iterator<Volume> blockVolumeItr = _dbClient.queryIterativeObjects(Volume.class, blockVolumeURIs); // Verify the LOCAL consistency group was properly migrated verifyConsistencyGroupMigration(localArrayCg, Types.LOCAL.name()); // Verify the volume migration took place correctly List<BlockObject> blockObjects = new ArrayList<BlockObject>(); while (blockVolumeItr.hasNext()) { blockObjects.add(blockVolumeItr.next()); } verifyBlockObjects(blockObjects); } /** * Verify the BlockMirror objects have been migrated correctly. * * @throws Exception */ private void verifyBlockSnapshotMigration() throws Exception { log.info("Verifying BlockSnapshot migration."); // Get the block snapshots Iterator<BlockSnapshot> blockSnapshotItr = _dbClient.queryIterativeObjects(BlockSnapshot.class, blockSnapshotURIs); List<BlockObject> blockObjects = new ArrayList<BlockObject>(); while (blockSnapshotItr.hasNext()) { blockObjects.add(blockSnapshotItr.next()); } verifyBlockObjects(blockObjects); } /** * Verify the BlockMirror objects have been migrated correctly. * * @throws Exception */ private void verifyBlockMirrorMigration() throws Exception { log.info("Verifying BlockMirror migration."); // Get the block snapshots Iterator<BlockMirror> blockMirrorItr = _dbClient.queryIterativeObjects(BlockMirror.class, blockMirrorURIs); List<BlockObject> blockObjects = new ArrayList<BlockObject>(); while (blockMirrorItr.hasNext()) { blockObjects.add(blockMirrorItr.next()); } verifyBlockObjects(blockObjects); } /** * Verify the migration of BlockConsistencyGroups. Ensure the type and deviceName * fields have been migrated to the types and deviceNames fields. * * @param consistencyGroup * @param types The types that should have been properly migrated. * @throws Exception */ private void verifyConsistencyGroupMigration(BlockConsistencyGroup consistencyGroup, String... types) throws Exception { log.info("Verifying BlockConsistencyGroup migration for " + consistencyGroup.getLabel()); // Verify that the primary CG now has the VPlex type added to its types list is and that // the type field null Assert.assertTrue("The BlockConsistencyGroup.type field should be null.", consistencyGroup.getType().equals(NullColumnValueGetter.getNullStr())); for (String type : types) { Assert.assertNotNull("The " + type + " BlockConsistencyGroup.types field should be populated.", consistencyGroup.getTypes()); Assert.assertTrue("The BlockConsistencyGroup.types field for " + consistencyGroup.getLabel() + " should contain " + type, consistencyGroup.getTypes().contains(type)); // Verify that the primary CG now has the RP and VPlex device names added to its list Assert.assertTrue("The local array BlockConsistencyGroup.deviceName field should be null.", consistencyGroup.getDeviceName().equals(NullColumnValueGetter.getNullStr())); } } /** * Verify the migration for BlockObjects. Ensure the consistencyGroups field has * been collapsed into the consistencyGroup field. * * @param blockObjects */ private void verifyBlockObjects(List<BlockObject> blockObjects) { for (BlockObject blockObject : blockObjects) { log.info("Verifying BlockObject migration for " + blockObject.getLabel()); // For RP+VPlex migrations, the BlockObjects will have a null consistencyGroups reference. // For non-RP+VPlex migrations, the BlockObjects will have an empty null consistencyGroups reference. // Both conditions indicate the field is no longer being used Assert.assertTrue("BlockObject.consistencyGroups field should be empty.", blockObject.getConsistencyGroups() == null || blockObject.getConsistencyGroups().isEmpty()); Assert.assertNotNull("BlockObject.consistencyGroup field should not be null.", blockObject.getConsistencyGroup()); } } /** * Verifies that ProtectionSets referencing all stale volumes get removed from the DB. */ private void verifyStaleProtectionSet() { ProtectionSet protectionSet = _dbClient.queryObject(ProtectionSet.class, staleProtectionSetURI); Assert.assertTrue("ProtectionSet " + staleProtectionSetURI + " is stale and should have been removed.", (protectionSet == null || protectionSet.getInactive())); } /** * Verifies that stale volumes have been removed from the ProtectionSet. */ private void verifyProtectionSetWith2StaleVolumes() { ProtectionSet protectionSet = _dbClient.queryObject(ProtectionSet.class, staleProtectionSetURI2); StringSet psVolumes = protectionSet.getVolumes(); for (String staleVolume : staleProtectionSetVolumeURIs) { // verify the stale volumes were removed Assert.assertFalse("ProtectionSet " + staleProtectionSetURI2 + " should not contain stale volume " + staleVolume, psVolumes.contains(staleVolume)); } } /** * Gets the VPlex cluster Id for a given VPlex virtual volume. * * @param virtualVolume * @return */ private String getVPlexClusterFromVolume(Volume virtualVolume) { String clusterId = null; if (virtualVolume != null && virtualVolume.getNativeId() != null) { String[] nativeIdSplit = virtualVolume.getNativeId().split("/"); clusterId = nativeIdSplit[2]; } return clusterId; } }