/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.server.upgrade.impl.callback;
import static com.emc.storageos.db.client.constraint.AlternateIdConstraint.Factory.getVolumesByConsistencyGroup;
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.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.BlockConsistencyGroup;
import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types;
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.StringSet;
import com.emc.storageos.db.client.model.TenantOrg;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.upgrade.BaseCustomMigrationCallback;
import com.emc.storageos.db.client.upgrade.callbacks.ProtectionSetToBlockConsistencyGroupMigration;
import com.emc.storageos.db.client.upgrade.callbacks.RpBlockSnapshotConsistencyGroupMigration;
import com.emc.storageos.db.client.upgrade.callbacks.VolumeRpJournalMigration;
import com.emc.storageos.db.server.DbsvcTestBase;
import com.emc.storageos.db.server.upgrade.DbSimpleMigrationTestBase;
/**
* Test proper population of the new DataObject.internalFlags field
*
* 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:
* - BlockSnapshotConsistencyGroupMigration
* - ProtectionSetToBlockConsistencyGroupMigration
* - VolumeRpJournalMigration
*/
public class RecoverPointConsistencyGroupMigrationTest extends DbSimpleMigrationTestBase {
private static final Logger log = LoggerFactory.getLogger(RecoverPointConsistencyGroupMigrationTest.class);
// Used for migrations tests related to RP BlockSnapshots.
private static List<URI> rpTestBlockSnapshotURIs = new ArrayList<URI>();
// Used for migrations tests related to RP ProtectionSets.
private static List<URI> rpTestProtectionSetURIs = new ArrayList<URI>();
// Used for migrations tests related to RP volumes.
private static List<URI> rpTestVolumeURIs = 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-";
@BeforeClass
public static void setup() throws IOException {
customMigrationCallbacks.put("1.1", new ArrayList<BaseCustomMigrationCallback>() {
{
add(new ProtectionSetToBlockConsistencyGroupMigration());
add(new VolumeRpJournalMigration());
add(new RpBlockSnapshotConsistencyGroupMigration());
}
});
DbsvcTestBase.setup();
}
@Override
protected String getSourceVersion() {
return "1.1";
}
@Override
protected String getTargetVersion() {
return "2.0";
}
@Override
protected void prepareData() throws Exception {
prepareRPConsistencyGroupData();
}
@Override
protected void verifyResults() throws Exception {
verifyBlockConsistencyGroupResults();
verifyBlockSnapshotResults();
}
/**
* Prepares the data for RP volume tests.
*
* @throws Exception When an error occurs preparing the RP volume data.
*/
private void prepareRPConsistencyGroupData() throws Exception {
log.info("Preparing RecoverPoint consistency group data for RecoverPointConsistencyGroupMigration");
TenantOrg tenantOrg = new TenantOrg();
URI tenantOrgURI = URIUtil.createId(TenantOrg.class);
tenantOrg.setId(tenantOrgURI);
_dbClient.createObject(tenantOrg);
Project proj = new Project();
URI projectURI = URIUtil.createId(Project.class);
String projectLabel = "project";
proj.setId(projectURI);
proj.setLabel(projectLabel);
proj.setTenantOrg(new NamedURI(tenantOrgURI, projectLabel));
_dbClient.createObject(proj);
// Create CG 1 volumes
ProtectionSet cg1ps = createProtectionSetData("cg1", projectURI);
List<Volume> cg1Volumes = createRpVolumes("cg1volume1", 1, cg1ps);
cg1Volumes.addAll(createRpVolumes("cg1volume2", 1, cg1ps));
addVolumesToProtectionSet(cg1ps.getId(), cg1Volumes);
createBlockSnapshotData("cg1Snap", cg1Volumes);
// Create CG 2 volumes
ProtectionSet cg2ps = createProtectionSetData("cg2", projectURI);
List<Volume> cg2Volumes = createRpVolumes("cg2volume1", 2, cg2ps);
cg2Volumes.addAll(createRpVolumes("cg2volume2", 2, cg2ps));
addVolumesToProtectionSet(cg2ps.getId(), cg2Volumes);
createBlockSnapshotData("cg2Snap", cg2Volumes);
// Create CG 3 volumes
ProtectionSet cg3ps = createProtectionSetData("cg3", projectURI);
List<Volume> cg3Volumes = createRpVolumes("cg3volume1", 3, cg3ps);
addVolumesToProtectionSet(cg3ps.getId(), cg3Volumes);
createBlockSnapshotData("cg3Snap", cg3Volumes);
// Verify the rp volume data exists in the database.
for (URI volumeURI : rpTestVolumeURIs) {
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
Assert.assertNotNull(String.format("RecoverPoint test volume %s not found", volumeURI), volume);
}
}
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);
}
/**
* 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) {
List<Volume> volumes = new ArrayList<Volume>();
String rsetName = "RSet-" + volumeName;
Volume sourceVolume = new Volume();
URI sourceVolumeURI = URIUtil.createId(Volume.class);
rpTestVolumeURIs.add(sourceVolumeURI);
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()));
_dbClient.createObject(sourceVolume);
Volume sourceVolumeJournal = new Volume();
URI sourceVolumeJournalURI = URIUtil.createId(Volume.class);
rpTestVolumeURIs.add(sourceVolumeJournalURI);
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()));
_dbClient.createObject(sourceVolumeJournal);
for (int i = 1; i <= numTargets; i++) {
Volume sourceVolumeTarget = new Volume();
URI sourceVolumeTargetURI = URIUtil.createId(Volume.class);
rpTestVolumeURIs.add(sourceVolumeTargetURI);
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()));
_dbClient.createObject(sourceVolumeTarget);
Volume sourceVolumeTargetJournal = new Volume();
URI sourceVolumeTargetJournalURI = URIUtil.createId(Volume.class);
rpTestVolumeURIs.add(sourceVolumeTargetJournalURI);
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()));
_dbClient.createObject(sourceVolumeTargetJournal);
}
return volumes;
}
private ProtectionSet createProtectionSetData(String cgName, URI projectURI) throws Exception {
ProtectionSet protectionSet = new ProtectionSet();
URI protectionSetURI = URIUtil.createId(ProtectionSet.class);
rpTestProtectionSetURIs.add(protectionSetURI);
protectionSet.setId(protectionSetURI);
protectionSet.setLabel("ViPR-" + cgName);
protectionSet.setProtectionId("790520997");
protectionSet.setProtectionStatus("ENABLED");
protectionSet.setProject(projectURI);
_dbClient.createObject(protectionSet);
return protectionSet;
}
private void createBlockSnapshotData(String name, List<Volume> volumes) throws Exception {
for (Volume volume : volumes) {
BlockSnapshot blockSnapshot = new BlockSnapshot();
URI blockSnapshotURI = URIUtil.createId(BlockSnapshot.class);
rpTestBlockSnapshotURIs.add(blockSnapshotURI);
blockSnapshot.setId(blockSnapshotURI);
blockSnapshot.setLabel(name);
blockSnapshot.setEmName(name);
blockSnapshot.setSnapsetLabel(name);
blockSnapshot.setEmBookmarkTime("1395408080019");
blockSnapshot.setEmInternalSiteName("0x5fd991b1295c0901");
blockSnapshot.setParent(new NamedURI(volume.getId(), name));
_dbClient.createObject(blockSnapshot);
}
}
/**
* Verifies the migration results for BlockConsistencyGroups.
*
* @throws Exception When an error occurs verifying the BlockConsistencyGroup
* migration results.
*/
private void verifyBlockConsistencyGroupResults() throws Exception {
log.info("Verifying created BlockConsistencyGroups for RecoverPointConsistencyGroupMigration.");
List<URI> cgURIs = _dbClient.queryByType(BlockConsistencyGroup.class, false);
Iterator<BlockConsistencyGroup> cgs =
_dbClient.queryIterativeObjects(BlockConsistencyGroup.class, cgURIs);
List<BlockConsistencyGroup> consistencyGroups = new ArrayList<BlockConsistencyGroup>();
while (cgs.hasNext()) {
consistencyGroups.add(cgs.next());
}
for (URI protectionSetURI : rpTestProtectionSetURIs) {
ProtectionSet protectionSet = _dbClient.queryObject(ProtectionSet.class, protectionSetURI);
Assert.assertNotNull(String.format("RecoverPoint test ProtectionSet %s not found", protectionSetURI), protectionSet);
// A BlockConsistencyGroup should have been created for each ProtectionSet
BlockConsistencyGroup cg = null;
for (BlockConsistencyGroup consistencyGroup : consistencyGroups) {
cg = consistencyGroup;
if (cg.getLabel().equals(protectionSet.getLabel())) {
break;
}
}
Project project = _dbClient.queryObject(Project.class, protectionSet.getProject());
Assert.assertTrue(String.format("Field deviceName is not properly set for consistency group %s", cg.getId().toString()),
cg.getDeviceName().equals(protectionSet.getLabel()));
Assert.assertTrue(String.format("Field type should be set to RP for consistency group %s", cg.getId().toString()),
cg.getType().equals(BlockConsistencyGroup.Types.RP.toString()));
Assert.assertTrue(String.format("Field project does not match the corresponding field for protection set %s", protectionSet
.getId().toString()),
project.getId().equals(protectionSet.getProject()));
Assert.assertTrue(String.format("Field tenant does not match the corresponding field for protection set %s", protectionSet
.getId().toString()),
cg.getTenant().getURI().equals(project.getTenantOrg().getURI()));
// Verify the consistency group volumes match the protection set volumes
// Find all volumes assigned to the group
final URIQueryResultList cgVolumesResults = new URIQueryResultList();
_dbClient.queryByConstraint(getVolumesByConsistencyGroup(cg.getId().toString()),
cgVolumesResults);
List<Volume> cgVolumes = new ArrayList<Volume>();
Iterator<URI> cgVolumeURIs = cgVolumesResults.iterator();
while (cgVolumeURIs.hasNext()) {
Volume cgVol = _dbClient.queryObject(Volume.class, cgVolumeURIs.next());
cgVolumes.add(cgVol);
}
for (Volume cgVolume : cgVolumes) {
log.info(String.format("CG (%s) volume (%s) found.", cg.getLabel(), cgVolume.getLabel()));
}
Iterator<String> protectionSetVolumeURIs = protectionSet.getVolumes().iterator();
while (protectionSetVolumeURIs.hasNext()) {
boolean found = false;
String protectionSetVolumeURI = protectionSetVolumeURIs.next();
for (Volume cgVolume : cgVolumes) {
if (cgVolume.getId().toString().equals(protectionSetVolumeURI)) {
found = true;
break;
}
}
Assert.assertTrue(
String.format("All ProtectionSet volumes MUST be part of the BlockConsistencyGroup. " +
"ProtectionSet volume %s was not found in BlockConsistencyGroup %s.",
protectionSetVolumeURI, cg.getId().toString()), found);
}
}
}
/**
* Verifies the migration results for BlockSnapshot.
*
* @throws Exception When an error occurs verifying the BlockSnapshot
* migration results.
*/
private void verifyBlockSnapshotResults() throws Exception {
log.info("Verifying updated BlockSnapshot results for RecoverPointConsistencyGroupMigration.");
for (URI blockSnapshotURI : rpTestBlockSnapshotURIs) {
BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, blockSnapshotURI);
// Make sure the consistency group has been set to match the parent volume's
// consistency group.
Volume parentVolume = _dbClient.queryObject(Volume.class, snapshot.getParent().getURI());
URI rpCgUri = parentVolume.fetchConsistencyGroupUriByType(_dbClient, Types.RP);
Assert.assertTrue("The block snapshot consistency group MUST match the parent volume's consistency group.",
snapshot.fetchConsistencyGroup().equals(rpCgUri));
}
}
}