/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource.utils;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
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.placement.VirtualPoolUtil;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringMap;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.VpoolProtectionVarraySettings;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.vpool.VirtualPoolChangeOperationEnum;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.util.VPlexUtil;
import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper;
import com.google.common.base.Joiner;
/**
* Compares VirtualPool for differences.
*/
public class VirtualPoolChangeAnalyzer extends DataObjectChangeAnalyzer {
// Various fields to compare with a VirtualPool
private static final String PROTECTION_VARRAY_SETTINGS = "protectionVarraySettings";
private static final String INACTIVE = "inactive";
private static final String ACLS = "acls";
private static final String FAST_EXPANSION = "fastExpansion";
private static final String NON_DISRUPTIVE_EXPANSION = "nonDisruptiveExpansion";
private static final String HIGH_AVAILABILITY = "highAvailability";
private static final String MIRROR_VPOOL = "mirrorVirtualPool";
private static final String REF_VPOOL = "refVirtualPool";
private static final String VARRAYS = "virtualArrays";
private static final String TYPE = "type";
private static final String IS_THIN_VOLUME_PRE_ALLOCATION_ENABLED = "isThinVolumePreAllocationEnabled";
private static final String THIN_VOLUME_PRE_ALLOCATION_PERCENTAGE = "thinVolumePreAllocationPercentage";
private static final String AUTO_TIER_POLICY_NAME = "autoTierPolicyName";
private static final String VMAX_COMPRESSION_ENABLED = "compressionEnabled";
private static final String UNIQUE_AUTO_TIERING_POLICY_NAMES = "uniquePolicyNames";
private static final String DRIVE_TYPE = "driveType";
private static final String ARRAY_INFO = "arrayInfo";
private static final String USE_MATCHED_POOLS = "useMatchedPools";
private static final String PROVISIONING_TYPE = "provisioningType";
private static final String PROTOCOLS = "protocols";
private static final String CREATION_TIME = "creationTime";
private static final String TAGS = "tags";
private static final String STATUS = "status";
private static final String NUM_PATHS = "numPaths";
private static final String INVALID_MATCHED_POOLS = "invalidMatchedPools";
private static final String MATCHED_POOLS = "matchedPools";
private static final String LABEL = "label";
private static final String HA_VARRAY_VPOOL_MAP = "haVarrayVpoolMap";
private static final String DESCRIPTION = "description";
private static final String ASSIGNED_STORAGE_POOLS = "assignedStoragePools";
private static final String REMOTECOPY_VARRAY_SETTINGS = "remoteProtectionSettings";
private static final String PATHS_PER_INITIATOR = "pathsPerInitiator";
private static final String MIN_PATHS = "minPaths";
private static final String NONE = "NONE";
private static final String HOST_IO_LIMIT_BANDWIDTH = "hostIOLimitBandwidth";
private static final String HOST_IO_LIMIT_IOPS = "hostIOLimitIOPs";
private static final String AUTO_CROSS_CONNECT_EXPORT = "autoCrossConnectExport";
private static final String RP_RPO_VALUE = "rpRpoValue";
private static final String RP_RPO_TYPE = "rpRpoType";
private static final String RP_COPY_MODE = "rpCopyMode";
private static final String HA_CONNECTED_TO_RP = "haVarrayConnectedToRp";
private static final String JOURNAL_SIZE = "journalSize";
private static final String JOURNAL_VARRAY = "journalVarray";
private static final String JOURNAL_VPOOL = "journalVpool";
private static final String MULTI_VOLUME_CONSISTENCY = "multivolumeconsistency";
private static final String METROPOINT = "metroPoint";
private static final String FILE_REPLICATION_TYPE = "fileReplicationType";
private static final String FILE_REPLICATION_COPIES = "fileRemoteCopySettings";
private static final String FILE_REPLICATION_RPO_VALUE = "frRpoValue";
private static final String FILE_REPLICATION_RPO_TYPE = "frRpoType";
private static final String FILE_REPLICATION_COPY_MODE = "replicationCopyMode";
private static final String FILE_REPLICATION_AT_PROJECT_LEVEL = "allowFilePolicyAtProjectLevel";
private static final String FILE_REPLICATION_AT_FS_LEVEL = "allowFilePolicyAtFSLevel";
private static final String FILE_REPLICATION_SUPPORTED = "fileReplicationSupported";
private static final String FILE_SNAPSHOT_SCHEDULE_SUPPORTED = "scheduleSnapshot";
private static final String STANDBY_JOURNAL_VARRAY = "standbyJournalVarray";
private static final String STANDBY_JOURNAL_VPOOL = "standbyJournalVpool";
private static final String[] INCLUDED_AUTO_TIERING_POLICY_LIMITS_COMPRESSION_CHANGE = new String[] { AUTO_TIER_POLICY_NAME,
HOST_IO_LIMIT_BANDWIDTH, HOST_IO_LIMIT_IOPS, VMAX_COMPRESSION_ENABLED };
private static final String[] EXCLUDED_AUTO_TIERING_POLICY_LIMITS_CHANGE = new String[] {
AUTO_TIER_POLICY_NAME, HOST_IO_LIMIT_BANDWIDTH, HOST_IO_LIMIT_IOPS,
VMAX_COMPRESSION_ENABLED, ARRAY_INFO,
UNIQUE_AUTO_TIERING_POLICY_NAMES, ASSIGNED_STORAGE_POOLS,
USE_MATCHED_POOLS, THIN_VOLUME_PRE_ALLOCATION_PERCENTAGE };
private static final String[] GENERALLY_EXCLUDED = { ACLS, DESCRIPTION,
LABEL, STATUS, TAGS, CREATION_TIME, INVALID_MATCHED_POOLS, MATCHED_POOLS, NON_DISRUPTIVE_EXPANSION };
private static final Logger s_logger = LoggerFactory
.getLogger(VirtualPoolChangeAnalyzer.class);
private static boolean newVpoolDoesNotSpecifyHaVpool = false;
/**
* Determines if the VPlex virtual volume vpool change is supported.
*
* @param volume A reference to the volume.
* @param currentVpool A reference to the current volume vpool.
* @param newVpool The desired new vpool.
* @param dbClient A reference to a DB client.
* @return Supported change vpool operations
*/
public static VirtualPoolChangeOperationEnum getSupportedVPlexVolumeVirtualPoolChangeOperation(Volume volume,
VirtualPool currentVpool, VirtualPool newVpool, DbClient dbClient,
StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking getSupportedVPlexVolumeVirtualPoolChangeOperation from [%s] to [%s]...",
currentVpool.getLabel(), newVpool.getLabel()));
// Make sure the VirtualPool's are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return null;
}
// Throw an exception if any of the following properties are different
// between the current and new vpool. The check for a highAvailability
// change is below. Going from local to distributed or distributed to
// local is not supported through this vpool change.
String[] include = new String[] { TYPE, VARRAYS,
REF_VPOOL,
FAST_EXPANSION, ACLS,
INACTIVE, NUM_PATHS };
// If current vpool specifies mirror then add MIRROR_VPOOL to include.
if (VirtualPool.vPoolSpecifiesMirrors(currentVpool, dbClient)) {
include = addElementToArray(include, MIRROR_VPOOL);
}
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, include, null, null);
if (!changes.isEmpty()) {
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return null;
}
if (VirtualPool.vPoolSpecifiesMirrors(newVpool, dbClient) &&
isSupportedAddMirrorsVirtualPoolChange(volume, currentVpool, newVpool, dbClient, notSuppReasonBuff)) {
return VirtualPoolChangeOperationEnum.ADD_MIRRORS;
}
return vplexCommonChecks(volume, currentVpool, newVpool, dbClient, notSuppReasonBuff, include);
}
/**
* Common checks for change vpool for all VPLEX volumes
*
* @param volume The volume in question
* @param currentVpool The current vpool of the volume
* @param newVpool The new/target vpool to change to
* @param dbClient DBClient ref
* @param notSuppReasonBuff String buffer to store reasons why a target vpool is excluded
* @param include A list of changes to include in the checks
* @return The supported vpool change operations
*/
private static VirtualPoolChangeOperationEnum vplexCommonChecks(Volume volume,
VirtualPool currentVpool, VirtualPool newVpool, DbClient dbClient,
StringBuffer notSuppReasonBuff, String[] include) {
s_logger.info(String.format("Checking vplexCommonChecks from [%s] to [%s]...", currentVpool.getLabel(), newVpool.getLabel()));
boolean isRPVPlex = VirtualPool.vPoolSpecifiesHighAvailability(currentVpool) && VirtualPool.vPoolSpecifiesRPVPlex(newVpool);
// If the volume is in a CG and the target vpool does not specify multi
// volume consistency, then the vpool change is not permitted.
if ((!NullColumnValueGetter.isNullURI(volume.getConsistencyGroup())) &&
(!newVpool.getMultivolumeConsistency())) {
notSuppReasonBuff
.append("The volume is in a consistency group but the target virtual pool does not specify multi-volume consistency");
return null;
}
// If highAvailability changed, we do support upgrade from
// vplex_local to vplex_distributed, with no other significant
// attributes changed. Otherwise the change is prohibited.
include = new String[] { HIGH_AVAILABILITY };
if (!analyzeChanges(currentVpool, newVpool, include, null, null).isEmpty()) {
// An ingested local VPLEX volume can't be converted to distributed.
// The backend volumes would need to be migrated first.
if (volume.isIngestedVolumeWithoutBackend(dbClient)) {
notSuppReasonBuff.append("The high availability of an ingested VPLEX volume cannot be modified.");
return null;
}
boolean isConvertToDistributed = isVPlexConvertToDistributed(currentVpool, newVpool, notSuppReasonBuff);
if (isConvertToDistributed && !VirtualPoolUtil.checkMatchingRemoteCopyVarraysettings(currentVpool, newVpool, dbClient)) {
isConvertToDistributed = false;
notSuppReasonBuff.append("Incompatible Remote Copy Varray Settings");
}
if (isConvertToDistributed) {
URI haVarrayURI = getHaVarrayURI(newVpool);
URI volumeVarray = volume.getVirtualArray();
URI cgURI = volume.getConsistencyGroup();
if (haVarrayURI.equals(volumeVarray)) {
// This is an upgrade from VPLEX local to VPLEX distributed.
// However, we only allow the upgrade if the HA varray
// specified in the new vpool is not the same as the varray
// for the volume. If they are the same, making this vpool
// change would cause us to try and create a distributed
// VPLEX volume with both legs of the distributed volume
// in the same cluster of the VPLEX, which would instead
// create a local mirror for the local VPLEX volume.
notSuppReasonBuff.append("The High Availability Virtual Array in the target " +
"Virtual Pool must be different than the Virtual Array for the volume.");
return null;
} else if (!NullColumnValueGetter.isNullURI(cgURI)) {
// We don't allow the upgrade when the volume is in a CG.
// The volumes in the CG must be of the same type.
// Attempting to upgrade one of them to distributed would
// result in an error, and chances are there are multiple
// volumes in the CG. We could allow the upgrade if there
// is only a single volume in the CG, but we would first
// have to remove the volume from the CG, update the CG
// properties to support distributed volumes, upgrade the
// volume, and then add it back to the CG. For now we are
// not supporting this as it seems like an edge use case.
// The user can always remove the volume from the CG,
// upgrade it to distributed, and then add it back to the
// CG manually if this is required.
notSuppReasonBuff
.append("The volume is in a consistency group");
return null;
} else {
if (!isRPVPlex) {
return VirtualPoolChangeOperationEnum.VPLEX_LOCAL_TO_DISTRIBUTED;
}
}
} else {
return null;
}
}
// For a distributed virtual volume, verify an HA vpool change,
// if any, also does not include any unsupported changes. If
// there is not a current HA vpool, it's a local virtual
// volume.
VirtualPool newHaVpool = null;
VirtualPool currentHaVpool = getHaVpool(currentVpool, dbClient);
if (currentHaVpool != null) {
// Get the new HA varray vpool.
try {
newHaVpool = getNewHaVpool(currentVpool, newVpool, dbClient);
} catch (Exception e) {
s_logger.error(e.getMessage());
notSuppReasonBuff.append(String.format("Could not get new HA vpool from [%s]", newVpool.getLabel()));
return null;
}
if (!currentHaVpool.getId().toString().equals(newHaVpool.getId().toString())) {
s_logger.info("HA varray vpools are different");
if (isRPVPlex && newVpoolDoesNotSpecifyHaVpool) {
// This means that new RPVPlex vpool does not specify
// an HA vpool, so we use the new vpool as the HA vpool, so
// specifying protection is OK.
include = new String[] { TYPE, VARRAYS,
REF_VPOOL, MIRROR_VPOOL,
FAST_EXPANSION, ACLS, INACTIVE, NUM_PATHS };
} else {
include = new String[] { TYPE, VARRAYS,
PROTECTION_VARRAY_SETTINGS, REF_VPOOL, MIRROR_VPOOL,
FAST_EXPANSION, ACLS, INACTIVE, NUM_PATHS };
}
Map<String, Change> changes = analyzeChanges(currentHaVpool, newHaVpool, include, null, null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("Changes in the following high availability virtual pool properties are not permitted: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return null;
}
}
}
// Finally, we check to see if any of the vpool changes will actually
// require data on the volume to be migrated. It's OK if the new vpool
// has a new label or description, but it must be accompanied by
// say a drive type for example. The purpose is not to support trivial
// changes, but instead those that would result in an actual change of
// the VPlex backend volumes.
boolean migrateHAVolume = false;
boolean migrateSourceVolume = VirtualPoolChangeAnalyzer
.vpoolChangeRequiresMigration(currentVpool, newVpool);
if (currentHaVpool != null) {
migrateHAVolume = VirtualPoolChangeAnalyzer.vpoolChangeRequiresMigration(
currentHaVpool, newHaVpool);
}
if (!migrateSourceVolume && !migrateHAVolume) {
notSuppReasonBuff.append("The virtual pool does not specify a change in any of the "
+ "following virtual pool properties: " + PROTOCOLS
+ ", " + PROVISIONING_TYPE
+ ", " + USE_MATCHED_POOLS
+ ", " + ARRAY_INFO
+ ", " + DRIVE_TYPE
+ ", " + AUTO_TIER_POLICY_NAME
+ ", " + HOST_IO_LIMIT_IOPS
+ ", " + HOST_IO_LIMIT_BANDWIDTH
+ ", " + IS_THIN_VOLUME_PRE_ALLOCATION_ENABLED
+ ", " + ASSIGNED_STORAGE_POOLS
+ ", " + VMAX_COMPRESSION_ENABLED);
if (!isRPVPlex) {
// For VPLEX, return null because the migrate operation is not valid.
// For RP+VPLEX, all this means is that there is no migration required
// when adding protection, continue on.
return null;
}
}
if (!isRPVPlex) {
return VirtualPoolChangeOperationEnum.VPLEX_DATA_MIGRATION;
}
return VirtualPoolChangeOperationEnum.RP_PROTECTED;
}
/**
* Gets the HA virtual pool for a VPLEX volume, if any, given the volume's
* virtual pool.
*
* @param vpool A reference to the volume's virtual pool.
* @param dbClient A reference to a DB client.
*
* @return The HA virtual pool for a VPLEX volume, if any, given the
* volume's virtual pool. Will be null if the volume's virtual pool
* does not specify VPLEX distributed HA.
*/
public static VirtualPool getHaVpool(VirtualPool vpool, DbClient dbClient) {
VirtualPool haVpool = null;
// The vpool must specify distributed HA.
if (VirtualPool.HighAvailabilityType.vplex_distributed.name().equals(vpool.getHighAvailability())) {
// The HA varray is required, so there should be an entry in the
// map. If a vpool was also specified the value in the map
// will not be a null URI.
StringMap haVpoolMap = vpool.getHaVarrayVpoolMap();
URI haVpoolURI = URI.create(haVpoolMap.values().iterator().next());
if (!NullColumnValueGetter.isNullURI(haVpoolURI)) {
haVpool = dbClient.queryObject(VirtualPool.class, haVpoolURI);
} else {
// When not specified, the same vpool is used on the HA side.
haVpool = vpool;
}
}
return haVpool;
}
/**
* Gets the new HA virtual pool for a VPLEX volume, if any, given the
* volume's current virtual pool and a new virtual pool to which it is being
* changed.
*
* @param currentVpool The current virtual pool for a VPLEX volume.
* @param newVpool A new virtual pool to which the volume will be changed.
* @param dbClient A reference to a DB client.
*
* @return The new HA virtual pool for a VPLEX volume, if any, that will
* result from changing the volume for its current virtual pool to
* the passed new virtual pool. Will return null if the current
* virtual pool does not specify VPLEX distributed HA.
*/
public static VirtualPool getNewHaVpool(VirtualPool currentVpool, VirtualPool newVpool, DbClient dbClient) {
newVpoolDoesNotSpecifyHaVpool = false;
VirtualPool newHaVpool = null;
// Get the HA varray specified by the current vpool. If it's
// null the current vpool does not specify VPLEX distributed HA.
URI haVarrayURI = getHaVarrayURI(currentVpool);
if (haVarrayURI != null) {
// RP+VPLEX only
if (NullColumnValueGetter.isNotNullValue(newVpool.getHaVarrayConnectedToRp())) {
haVarrayURI = URI.create(newVpool.getHaVarrayConnectedToRp());
}
StringMap newHaVpoolMap = newVpool.getHaVarrayVpoolMap();
// The new vpool must specify an entry for the HA varray.
if (newHaVpoolMap.containsKey(haVarrayURI.toString())) {
URI newHaVpoolURI = URI.create(newHaVpoolMap.get(haVarrayURI.toString()));
if (!NullColumnValueGetter.isNullURI(newHaVpoolURI)) {
newHaVpool = dbClient.queryObject(VirtualPool.class, newHaVpoolURI);
} else {
// When no HA vpool is specified in the new vpool,
// the new HA vpool is the new vpool itself.
newHaVpool = newVpool;
newVpoolDoesNotSpecifyHaVpool = true;
}
} else {
throw APIException.badRequests.wrongHighAvailabilityVArrayInVPool(haVarrayURI.toString());
}
}
return newHaVpool;
}
/**
* Gets the HA virtual array specified by the passed virtual pool.
*
* @param vpool A reference to the virtual pool.
*
* @return The HA virtual array specified by the passed virtual pool
* or null if the virtual pool does not specify VPLEX distributed HA.
*/
public static URI getHaVarrayURI(VirtualPool vpool) {
URI haVarrayURI = null;
if (VirtualPool.HighAvailabilityType.vplex_distributed.name().equals(vpool.getHighAvailability())) {
StringMap newHaVpoolMap = vpool.getHaVarrayVpoolMap();
// The HA virtual array is required, so there should be an
// entry in the map.
haVarrayURI = URI.create(newHaVpoolMap.keySet().iterator().next());
}
return haVarrayURI;
}
/**
* Determines if the two Vpool are equivalent by examining the modified
* fields.
*
* @param currentVpool A reference to the current volume Vpool.
* @param newVpool The desired new Vpool.
*
* @return true if equivalent, false otherwise.
*/
public static boolean vpoolChangeRequiresMigration(VirtualPool currentVpool, VirtualPool newVpool) {
s_logger.info(String.format("Checking vpoolChangeRequiresMigration from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
// Verify they are different vpools
if (isSameVirtualPool(currentVpool, newVpool)) {
return false;
}
// Analyze the changes to determine if there are any changes that
// require a migration. We assume that the following changes would
// require a migration of the data: protocols,
// provisioningType, useMatchedPools, arrayInfo, driveType,
// autoTierPolicyName, host io limits, host io bandwidth,
// thin volume allocation, assigned storage pools.
String[] include = new String[] { PROTOCOLS, PROVISIONING_TYPE,
USE_MATCHED_POOLS, ARRAY_INFO,
DRIVE_TYPE, AUTO_TIER_POLICY_NAME, HOST_IO_LIMIT_IOPS, HOST_IO_LIMIT_BANDWIDTH, VMAX_COMPRESSION_ENABLED,
IS_THIN_VOLUME_PRE_ALLOCATION_ENABLED,
ASSIGNED_STORAGE_POOLS };
return !analyzeChanges(currentVpool, newVpool, include, null, null).isEmpty();
}
/**
* Returns true if the difference between vpool1 and vpool2 is that vpool2 is
* requesting highAvailability. Note that remoteProtectionSettings are not checked.
*
* @param vpool1 Reference to Vpool to compare.
* @param vpool2 Reference to Vpool to compare.
* @param notImportReasonBuff [OUT] Specifies reason why its not an import.
*
* @return true if the Vpool difference indicates vpool2 adds VPlex high
* availability, false otherwise.
*/
public static boolean isVPlexImport(Volume volume, VirtualPool vpool1, VirtualPool vpool2,
StringBuffer notImportReasonBuff) {
s_logger.info(String.format("Checking isVPlexImport from [%s] to [%s]...", vpool1.getLabel(), vpool2.getLabel()));
if (null != volume.getMirrors() && !volume.getMirrors().isEmpty()) {
notImportReasonBuff.append(String.format("Volume [%s] has continuous copies attached. "
+ "Change vpool for a volume which has continuous copies is not allowed.", volume.getLabel()));
return false;
}
String[] excluded = new String[] { ACLS, ASSIGNED_STORAGE_POOLS, DESCRIPTION,
HA_VARRAY_VPOOL_MAP, LABEL, MATCHED_POOLS, INVALID_MATCHED_POOLS, NUM_PATHS,
STATUS, TAGS, CREATION_TIME, THIN_VOLUME_PRE_ALLOCATION_PERCENTAGE,
NON_DISRUPTIVE_EXPANSION, AUTO_CROSS_CONNECT_EXPORT, MIRROR_VPOOL, REMOTECOPY_VARRAY_SETTINGS };
Map<String, Change> changes = analyzeChanges(vpool1, vpool2, null, excluded, null);
// Note that we assume vpool1 is for a non-vplex volume and
// does not specify a value for high availability. This is
// the case when the function is called.
if ((changes.size() == 1) && changes.get(HIGH_AVAILABILITY) != null) {
return true;
}
int changeCount = changes.size();
// Set the reason why the vpool change is not a VPlex import.
if ((changeCount == 0) || (changes.get(HIGH_AVAILABILITY) == null)) {
notImportReasonBuff.append("The target virtual pool does not specify a value for high "
+ "availability");
} else if (changeCount > 1) {
// render output for error message
StringBuffer changesOutput = new StringBuffer();
for (Change change : changes.values()) {
if (!change._key.equals(HIGH_AVAILABILITY)) {
// CTRL-12010 if the source vpool's rpo value is null,
// it's fine if the target vpool has a value of 0
if (change._key.equals(RP_RPO_VALUE) && (change._left == null)) {
if (change._right != null && (Long.valueOf(change._right.toString()) == 0)) {
s_logger.info("rpRpoValue diff is okay");
changeCount--;
continue;
}
}
changesOutput.append(change.toString() + " ");
}
}
if (changeCount == 1) {
s_logger.info("there were some differences, but changes that don't "
+ "matter were filtered out, so this vpool is okay for VPLEX");
return true;
}
notImportReasonBuff.append("The target virtual pool contains differences in properties "
+ "other than high availability.");
s_logger.info("The target virtual pool contains differences in properties "
+ "other than high availability.: " + changesOutput.toString().trim());
}
return false;
}
/**
* Returns true if the new virtual pool contains the storage pool of the
* volume requested for virtual pool change.
*
* @param volume the volume requested for virtual pool change
* @param newVpool the target virtual pool
* @param notSuppReasonBuff [OUT] contains the reasons a virtual pool
* change is not supported
*
* @return true if the target virtual pool contains the storage pool of
* the volume requested for virtual pool change
*/
public static boolean doesVplexVpoolContainVolumeStoragePool(Volume volume,
VirtualPool newVpool, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking doesVplexVpoolContainVolumeStoragePool for [%s]...", newVpool.getLabel()));
StringSet poolsToCheck = newVpool.getUseMatchedPools() ? newVpool.getMatchedStoragePools() : newVpool.getAssignedStoragePools();
if ((null == poolsToCheck) || !poolsToCheck.contains(volume.getPool().toString())) {
notSuppReasonBuff.append("The target virtual pool ").append(newVpool.getLabel());
notSuppReasonBuff.append(" does not contain the volume's storage pool (");
notSuppReasonBuff.append(volume.getPool()).append(") ");
return false;
}
return true;
}
/**
* Returns true iff the only difference is converting from a vplex_local to
* vplex_distributed. Note that remoteProtectionSettings are not checked.
*
* @param vpool1 A reference to a Vpool
* @param vpool2 A reference to a Vpool
* @param notSuppReasonBuff [OUT] Specifies the reason a Vpool change is not
* supported between the two Vpool.
*
* @return true if the Vpool difference specifies only a change in the high
* availability type from local to distributed, false otherwise.
*/
public static boolean isVPlexConvertToDistributed(VirtualPool vpool1, VirtualPool vpool2,
StringBuffer notSuppReasonBuff) {
s_logger.info("Running isVPlexConvertToDistributed...");
s_logger.info(String.format("Checking isVPlexConvertToDistributed from [%s] to [%s]...", vpool1.getLabel(), vpool2.getLabel()));
String[] excluded = new String[] { ASSIGNED_STORAGE_POOLS, DESCRIPTION,
HA_VARRAY_VPOOL_MAP, LABEL, MATCHED_POOLS, INVALID_MATCHED_POOLS, NUM_PATHS,
STATUS, TAGS, CREATION_TIME, NON_DISRUPTIVE_EXPANSION, REMOTECOPY_VARRAY_SETTINGS };
Map<String, Change> changes = analyzeChanges(vpool1, vpool2, null, excluded, null);
// changes.size() needs to be greater than 1, because at least
// HIGH_AVAILABILITY change is expected
if (changes.size() > 1) {
notSuppReasonBuff.append("Changes in addition to a change in the "
+ "virtual pool high availability property are not permitted.");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
s_logger.info("Changes in addition to a change in the "
+ "virtual pool high availability property are not permitted: ");
for (Entry<String, Change> entry : changes.entrySet()) {
// ignore HIGH_AVAILABILITY change because it is expected here
if (entry.getKey().equals(HIGH_AVAILABILITY)) {
continue;
}
s_logger.info(entry.getKey() + "- " + entry.getValue() + " ");
}
return false;
}
Change change = changes.get(HIGH_AVAILABILITY);
if (change != null
&& change._left != null
&& change._left.equals(VirtualPool.HighAvailabilityType.vplex_local.name())
&& change._right != null
&& change._right.equals(VirtualPool.HighAvailabilityType.vplex_distributed.name())) {
return true;
} else {
notSuppReasonBuff.append(String.format(
"The virtual pool high availability property can only be changed from %s to %s",
VirtualPool.HighAvailabilityType.vplex_local.name(),
VirtualPool.HighAvailabilityType.vplex_distributed.name()));
return false;
}
}
/**
* Verifies the Vpool change for a tech refresh of a VPlex virtual volume.
* The Vpool should only specify a simple change such as the type of disk
* drive.
*
* @param srcVpool The Vpool of the migration source
* @param tgtVpool The proposed Vpool of the migration target.
*/
public static void verifyVirtualPoolChangeForTechRefresh(VirtualPool srcVpool, VirtualPool tgtVpool) {
// Exclude things that we don't mind changing in the Vpool
// and check if there are any other changes. If so, the
// Vpool requested is not valid. What I really want to allow
// for tech refresh are changes such as driveType,
// protocols, provisioningType, and arrayInfo. However,
// there will be other benign changes that can occur.
String[] exclude = new String[] { PROTOCOLS, PROVISIONING_TYPE, ARRAY_INFO,
DRIVE_TYPE, AUTO_TIER_POLICY_NAME, HOST_IO_LIMIT_IOPS, HOST_IO_LIMIT_BANDWIDTH, VMAX_COMPRESSION_ENABLED, MATCHED_POOLS,
INVALID_MATCHED_POOLS,
ASSIGNED_STORAGE_POOLS, LABEL, DESCRIPTION, STATUS, TAGS,
CREATION_TIME, NON_DISRUPTIVE_EXPANSION };
if (!VirtualPoolChangeAnalyzer.analyzeChanges(srcVpool, tgtVpool, null, exclude, null)
.isEmpty()) {
throw APIException.badRequests.vPoolChangeNotValid(srcVpool.getId(), tgtVpool.getId());
}
}
/**
* Determines if the volume qualifies for RP protection. (and if not, why not)
*
* @param volume A reference to the volume.
* @param currentVpool A reference to the current volume Vpool.
* @param newVpool The desired new Vpool.
* @param dbClient A reference to a DB client.
* @param notSuppReasonBuff [OUT] Specifies the reason a Vpool change is not
* supported between the two Vpool.
* @return true if the add RP protection operation is allowed, false otherwise.
*/
public static boolean isSupportedAddRPProtectionVirtualPoolChange(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
DbClient dbClient, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedAddRPProtectionVirtualPoolChange from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
// Make sure the Vpool are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// RP protection already exists
if (volume.checkForRp() || VirtualPool.vPoolSpecifiesProtection(currentVpool)) {
notSuppReasonBuff.append("Can't add RecoverPoint Protection since it already exists.");
return false;
}
// Adding RP+VPLEX/MetroPoint protection to a non-VPLEX volume is not supported
if (!VirtualPool.vPoolSpecifiesHighAvailability(currentVpool) && VirtualPool.vPoolSpecifiesRPVPlex(newVpool)) {
notSuppReasonBuff.append("Can't add RecoverPoint+VPLEX Protection directly to non-VPLEX volume. Import to VPLEX first.");
return false;
}
// Adding MetroPoint protection to a VPLEX Local volume is not supported
if (VirtualPool.vPoolSpecifiesHighAvailability(currentVpool)
&& !VirtualPool.vPoolSpecifiesHighAvailabilityDistributed(currentVpool)
&& VirtualPool.vPoolSpecifiesMetroPoint(newVpool)) {
notSuppReasonBuff.append("Can't add MetroPoint Protection directly to VPLEX Local volume. "
+ "Upgrade from VPLEX Local to VPLEX Distributed first.");
return false;
}
// Throw an exception if any of the following properties are different
// between the current and new Vpool. The check for continuous and protection-
// based settings change is below.
String[] include = new String[] { TYPE, VARRAYS,
REF_VPOOL, MIRROR_VPOOL,
FAST_EXPANSION, ACLS,
INACTIVE, PROTOCOLS, PROVISIONING_TYPE,
USE_MATCHED_POOLS, ARRAY_INFO,
DRIVE_TYPE, AUTO_TIER_POLICY_NAME, HOST_IO_LIMIT_IOPS,
HOST_IO_LIMIT_BANDWIDTH, VMAX_COMPRESSION_ENABLED,
IS_THIN_VOLUME_PRE_ALLOCATION_ENABLED,
ASSIGNED_STORAGE_POOLS };
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, include, null, null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
// If protection changed, we do support upgrade from
// non-protected to protected. Make sure that change is there.
include = new String[] { PROTECTION_VARRAY_SETTINGS };
changes = analyzeChanges(currentVpool, newVpool, include, null, null);
if (changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are required: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
// Check for RP+VPLEX/MP case where we would protect a VPLEX Virtual Volume as long
// as the new vpool specifies HA and Protection both.
if (VirtualPool.vPoolSpecifiesHighAvailability(currentVpool)
&& (VirtualPool.vPoolSpecifiesRPVPlex(newVpool)
|| VirtualPool.vPoolSpecifiesMetroPoint(newVpool))) {
VirtualPoolChangeOperationEnum op = vplexCommonChecks(volume, currentVpool, newVpool, dbClient, notSuppReasonBuff, include);
if (op == null || !op.equals(VirtualPoolChangeOperationEnum.RP_PROTECTED)) {
return false;
}
}
return true;
}
/**
* Determines if the volume qualifies for SRDF protection. (and if not, why not)
*
* @param volume A reference to the volume.
* @param currentVpool A reference to the current volume Vpool.
* @param newVpool The desired new Vpool.
* @param dbClient A reference to a DB client.
* @return true if SRDF virtual pool change is allowed
*/
public static boolean isSupportedSRDFVolumeVirtualPoolChange(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
DbClient dbClient, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedSRDFVolumeVirtualPoolChange from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
// Make sure that the volume is vmax volume. Only vmax supports srdf protection.
URI storageDeviceURI = volume.getStorageController();
StorageSystem storageSystem = dbClient.queryObject(StorageSystem.class, storageDeviceURI);
if (!(DiscoveredDataObject.Type.vmax.name().equals(storageSystem.getSystemType()))) {
notSuppReasonBuff.append("Volume is not VMAX volume.");
return false;
}
// Make sure the Vpool are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// Throw an exception if any of the following properties are different
// between the current and new Vpool. The check for continuous and protection-
// based settings change is below.
String[] include = new String[] { TYPE, VARRAYS,
REF_VPOOL, MIRROR_VPOOL, HIGH_AVAILABILITY, PROTECTION_VARRAY_SETTINGS,
FAST_EXPANSION, ACLS, INACTIVE,
NUM_PATHS, PATHS_PER_INITIATOR, MIN_PATHS,
AUTO_TIER_POLICY_NAME, HOST_IO_LIMIT_BANDWIDTH, HOST_IO_LIMIT_IOPS, VMAX_COMPRESSION_ENABLED };
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, include, null, null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
s_logger.info("Virtual Pool change not supported: {}", notSuppReasonBuff.toString());
return false;
}
// TODO: SYSTEM_TYPE can change, but only from ANY to VMAX, but not VMAX to VNX or anything to VNX CTRL-276
// If protection changed, we do support upgrade from
// non-protected to protected. Make sure that change is there.
include = new String[] { REMOTECOPY_VARRAY_SETTINGS };
changes = analyzeChanges(currentVpool, newVpool, include, null, null);
if (changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are required: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
return true;
}
/**
* Determine if the volume qualifies for the addition of continuous copies.
*
* @param volume
* @param currentVpool
* @param newVpool
* @param dbClient
* @param notSuppReasonBuff
* @return
*/
public static boolean isSupportedAddMirrorsVirtualPoolChange(Volume volume, VirtualPool currentVpool,
VirtualPool newVpool, DbClient dbClient,
StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedAddMirrorsVirtualPoolChange from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
if (VirtualPool.HighAvailabilityType.vplex_distributed.name().equals(currentVpool.getHighAvailability()) &&
VirtualPool.HighAvailabilityType.vplex_distributed.name().equals(newVpool.getHighAvailability())) {
return isSupportedAddMirrorsVirtualPoolChangeForVplexDistributed(volume, currentVpool, newVpool, dbClient, notSuppReasonBuff);
}
// Throw an exception if any of the following properties are different
// between the current and new VirtualPool. The check for continuous and protection-
// based settings change is below.
String[] include = new String[] { TYPE, VARRAYS, REF_VPOOL, HIGH_AVAILABILITY, PROTECTION_VARRAY_SETTINGS,
FAST_EXPANSION, ACLS, INACTIVE, DRIVE_TYPE, ARRAY_INFO, PROVISIONING_TYPE, PROTOCOLS };
// Throw an exception if any of the following properties values from current vpool
// are not contained in new virtual pool
String[] contain = new String[] { MATCHED_POOLS, ASSIGNED_STORAGE_POOLS };
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, include, null, contain);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
return true;
}
/**
* This method is used for the VPLEX Distributed volume to check if add mirror(s) is
* supported by changing vpool to newVpool.
*
* @param volume The reference to the volume
* @param currentVpool The reference to the current virtual pool for the volume
* @param newVpool The reference to new virtual pool
* @param dbClient an instance of {@link DbClient}
* @param notSuppReasonBuff out param for not supported reason
* @return true if add mirrors supported else false
*/
private static boolean isSupportedAddMirrorsVirtualPoolChangeForVplexDistributed(Volume volume, VirtualPool currentVpool,
VirtualPool newVpool, DbClient dbClient,
StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedAddMirrorsVirtualPoolChangeForVplexDistributed from [%s] to [%s]...",
currentVpool.getLabel(), newVpool.getLabel()));
boolean supported = false;
// This if section is basically checking source side vpool to make sure its good to be changed with newVpool
if (newVpool.getMaxNativeContinuousCopies() > 0 && newVpool.getMirrorVirtualPool() != null) {
String[] include = new String[] { TYPE, VARRAYS, REF_VPOOL, HIGH_AVAILABILITY, PROTECTION_VARRAY_SETTINGS,
FAST_EXPANSION, ACLS, INACTIVE, DRIVE_TYPE, ARRAY_INFO, PROVISIONING_TYPE, PROTOCOLS };
// Throw an exception if any of the following properties values from current vpool
// are not contained in new virtual pool
String[] contain = new String[] { MATCHED_POOLS, ASSIGNED_STORAGE_POOLS };
// analyzeChanges will ensure that elements in the include list donot change between
// currentVpool and newVpool and elements in the contain list must be present in the
// newVpool plus newVpool can contain extra other storage pools in those two properties.
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, include, null, contain);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
supported = true;
}
// This section is checking HA side vpool to make sure its good to be changed with the newVpool Ha Vpool
VirtualPool currentHaVpool = VirtualPool.getHAVPool(currentVpool, dbClient);
if (currentHaVpool == null) {
currentHaVpool = currentVpool;
}
VirtualPool newHaVpool = VirtualPool.getHAVPool(newVpool, dbClient);
if (currentHaVpool != null && newHaVpool != null) {
if (newHaVpool.getMaxNativeContinuousCopies() > 0 && newHaVpool.getMirrorVirtualPool() != null) {
String[] include = new String[] { TYPE, VARRAYS, REF_VPOOL, PROTECTION_VARRAY_SETTINGS,
FAST_EXPANSION, ACLS, INACTIVE, DRIVE_TYPE, ARRAY_INFO, PROVISIONING_TYPE, PROTOCOLS };
// Throw an exception if any of the following properties values from current vpool
// are not contained in new virtual pool
String[] contain = new String[] { MATCHED_POOLS, ASSIGNED_STORAGE_POOLS };
// If HaVpool for the newVpool is enabled for continuous copies then we need
// to ensure that some of the properties as mentioned in the include list do
// not change for the ha vpool and values for the properties in contain list
// are present in the newVpool haVpool plus it can contain extra values too.
// Note we don't need to check for HIGH_AVAILABILITY property in case of HA Vpool.
// Before user could have ha Vpool with no HA set and now can move to HA set with
// continuous copies enabled.
Map<String, Change> changes = analyzeChanges(currentHaVpool, newHaVpool, include, null, contain);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
supported = true;
}
}
return supported;
}
/**
* Checks to see if only the Export Path Params have changed.
*
* @param volume The volume to check
* @param currentVpool The volume's current vpool
* @param newVpool The target vpool
* @param dbClient DBClient reference
* @param notSuppReasonBuff Buffer to store reasons for not being supported
* @return true is path params changes are allowed, false otherwise
*/
public static boolean isSupportedPathParamsChange(Volume volume,
VirtualPool currentVpool, VirtualPool newVpool, DbClient dbClient, StringBuffer notSuppReasonBuff) {
// Make sure the VirtualPool's are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// First, check that either NUM_PATHS or PATHS_PER_INITIATOR changed
String[] included = new String[] { NUM_PATHS, PATHS_PER_INITIATOR };
if (analyzeChanges(currentVpool, newVpool, included, null, null).isEmpty()) {
notSuppReasonBuff.append("Did not change MAX_PATHS or PATHS_PER_INITIATOR. ");
return false;
}
// Second, check that nothing other than the excluded attributes changed.
List<String> excluded = new ArrayList<String>();
String[] exclude = new String[] { NUM_PATHS, PATHS_PER_INITIATOR, MIN_PATHS,
THIN_VOLUME_PRE_ALLOCATION_PERCENTAGE };
excluded.addAll(Arrays.asList(exclude));
excluded.addAll(Arrays.asList(GENERALLY_EXCLUDED));
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, null, excluded.toArray(exclude), null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
s_logger.info("Virtual Pool change not supported, "
+ "these target pool differences are invalid: {}", notSuppReasonBuff.toString());
return false;
}
return true;
}
/**
* Check to see if the current VirtualPool is the same as the requested VirtualPool.
*
* @param current The current vpool
* @param requested The target vpool
* @param notSuppReasonBuff Buffer to store reasons for not being supported
* @return true if the vpools are the same, false otherwise
*/
public static boolean isSameVirtualPool(VirtualPool current, VirtualPool requested, StringBuffer notSuppReasonBuff) {
if (current.getId().equals(requested.getId())) {
String msg = String.format("The target virtual pool [%s] is the same as current virtual pool.", requested.getLabel());
s_logger.info(msg);
if (notSuppReasonBuff != null) {
notSuppReasonBuff.append(msg);
}
return true;
}
return false;
}
private static boolean isSameVirtualPool(VirtualPool current, VirtualPool requested) {
return isSameVirtualPool(current, requested, null);
}
/**
* Checks to see if the replication mode change is supported.
*
* @param currentVpool the source virtual pool
* @param newVpool the target virtual pool
* @param notSuppReasonBuff the not supported reason string buffer
* @return true if replication mode change is supported, false otherwise
*/
public static boolean isSupportedReplicationModeChange(VirtualPool currentVpool, VirtualPool newVpool, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedReplicationModeChange from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
// Make sure the VirtualPool's are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// Both the source and target vpools must specify RP protection.
// NOTE: If support for SRDF is added in the future, we must modify the conditions
if (!VirtualPool.vPoolSpecifiesProtection(currentVpool) || !VirtualPool.vPoolSpecifiesProtection(newVpool)) {
notSuppReasonBuff
.append(String
.format("Replication Mode virtual pool change is not supported for target virtual pool %s. Cannot modify the replication mode if both the source and target vpools do not specify RP protection.",
newVpool.getLabel()));
s_logger.info(notSuppReasonBuff.toString());
return false;
}
// First, check that RP_COPY_MODE changed.
String[] included = new String[] { RP_COPY_MODE };
if (analyzeChanges(currentVpool, newVpool, included, null, null).isEmpty()) {
notSuppReasonBuff
.append(String
.format(
"Replication Mode virtual pool change is not supported for target virtual pool %s. There is no change in replication mode.",
newVpool.getLabel()));
s_logger.info(notSuppReasonBuff.toString());
return false;
}
// Check that nothing other than the excluded attributes changed.
List<String> excluded = new ArrayList<String>();
String[] exclude = new String[] { RP_COPY_MODE, RP_RPO_VALUE, RP_RPO_TYPE, PROTECTION_VARRAY_SETTINGS };
excluded.addAll(Arrays.asList(exclude));
excluded.addAll(Arrays.asList(GENERALLY_EXCLUDED));
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, null, excluded.toArray(exclude), null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append(String.format("These target virtual pool differences are invalid: "));
fillInNotSupportedReasons(changes, notSuppReasonBuff);
s_logger.info(String.format("Replication Mode virtual pool change not supported. %s. Parameters other than %s were changed.",
notSuppReasonBuff.toString(), excluded.toString()));
return false;
}
return true;
}
/**
* Checks to see if only the Auto-tiering policy and/or host io limits (only for vmax) has changed.
*
* @param volume the volume
* @param currentVpool the current vPool
* @param newVpool the new vPool
* @param _dbClient the _db client
* @param notSuppReasonBuff the not supported reason buff
* @return true, if it is supported Auto-tiering policy and/or host io limits (only for vmax)change
*/
public static boolean isSupportedAutoTieringPolicyAndLimitsChange(Volume volume,
VirtualPool currentVpool, VirtualPool newVpool, DbClient _dbClient,
StringBuffer notSuppReasonBuff) {
/**
* Case 1 : from one Auto-tiering Policy (current vPool) to another Auto-tiering policy (new vPool).
* - System type in both vPools should remain same.
* Case 2 : from NONE (no Auto-tiering policy in current vPool) to some Auto-tiering policy (new vPool).
* - System type should remain same
* (take source system type from either volume or current vPool).
* Case 3 : from some Auto-tiering policy (current vPool) to NONE (no Auto-tiering policy in new vPool).
* - no need to check whether system type has changed.
* Case 4 : from host io limit bandwidth and/or iops fron current vpool are different from new vPool.
*
*/
s_logger.info(String.format("Checking isSupportedAutoTieringPolicyAndLimitsChange from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
// Make sure the VirtualPool's are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// First, check that AUTO_TIER_POLICY_NAME changed.
String[] included = INCLUDED_AUTO_TIERING_POLICY_LIMITS_COMPRESSION_CHANGE;
if (analyzeChanges(currentVpool, newVpool, included, null, null).isEmpty()) {
notSuppReasonBuff
.append("Did not change AUTO_TIER_POLICY_NAME, HOST_IO_LIMIT_BANDWIDTH, HOST_IO_LIMIT_IOPS or VMAX_AF_COMPRESSION");
return false;
}
// Second, check system type field.
StringSet currentSystemType = null;
StringSet newSystemType = null;
if (currentVpool.getArrayInfo() != null) {
currentSystemType = (StringSet) currentVpool.getArrayInfo()
.get(VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE).clone();
}
// if current vPool is not associated with any system type, get it from volume.
if (currentSystemType == null || currentSystemType.isEmpty() || currentSystemType.contains(NONE)) {
URI systemURI = volume.getStorageController();
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
if (currentSystemType == null) {
currentSystemType = new StringSet();
}
currentSystemType.remove(NONE);
currentSystemType.add(system.getSystemType());
}
if (newVpool.getArrayInfo() != null) {
newSystemType = newVpool.getArrayInfo().get(VirtualPoolCapabilityValuesWrapper.SYSTEM_TYPE);
}
// compare system type
if (newSystemType != null && !newSystemType.contains(NONE)) {
if (!currentSystemType.equals(newSystemType)) {
notSuppReasonBuff
.append("Auto-tiering Policy change: system_type between source vPool/Volume and target vPool is not same.");
return false; // system type not same
}
}
// Third, check that target vPool has volume's storage pool in its matched pools list.
// if target vPool has manual pool selection enabled, then volume's pool should be in assigned pools list.
if (!checkTargetVpoolHasVolumePool(volume, currentVpool, newVpool, _dbClient)) {
String msg = "Auto-tiering Policy change: Target vPool does not have Volume's Storage Pool in its matched/assigned pools list.";
if (VirtualPool.vPoolSpecifiesHighAvailability(currentVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(newVpool)) {
msg = "Auto-tiering Policy change: Target Vplex/Vplex HA vPool does not have Volume's Storage Pool in its matched/assigned pools list.";
}
notSuppReasonBuff.append(msg);
s_logger.info("Virtual Pool change not supported: {}", notSuppReasonBuff.toString());
return false;
}
// Last, check that nothing other than the excluded attributes changed.
List<String> excluded = new ArrayList<String>();
String[] exclude = EXCLUDED_AUTO_TIERING_POLICY_LIMITS_CHANGE;
excluded.addAll(Arrays.asList(exclude));
excluded.addAll(Arrays.asList(GENERALLY_EXCLUDED));
// PROTECTION_VARRAY_SETTINGS changes every time a vpool is duplicated so we will ignore it, otherwise
// this change vpool operation is blocked.
// RP_RPO_VALUE will be updated from null to 0 if any vpool update is performed so we will ignore it,
// otherwise this change vpool operation is blocked.
excluded.addAll(Arrays.asList(RP_RPO_VALUE, PROTECTION_VARRAY_SETTINGS));
if (VirtualPool.vPoolSpecifiesHighAvailabilityDistributed(currentVpool)
&& VirtualPool.vPoolSpecifiesHighAvailabilityDistributed(newVpool)) {
// get current & new HA vPools and compare
VirtualPool currentHAVpool = getHaVpool(currentVpool, _dbClient);
VirtualPool newHAVpool = getHaVpool(newVpool, _dbClient);
if (!isSameVirtualPool(currentHAVpool, newHAVpool)) {
s_logger.info("Comparing HA vPool attributes {} {}", currentHAVpool.getLabel(), newHAVpool.getLabel());
Map<String, Change> changes = analyzeChanges(currentHAVpool, newHAVpool, null, excluded.toArray(exclude), null);
if (!changes.isEmpty()) {
logNotSupportedReasonForTieringPolicyChange(changes, notSuppReasonBuff, exclude, "HA vPool");
return false;
}
}
// ignore VPLEX HA vArray/vPool settings difference when the new vPool satisfies Tiering Policy change
excluded.add(HA_VARRAY_VPOOL_MAP);
}
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, null, excluded.toArray(exclude), null);
if (!changes.isEmpty()) {
logNotSupportedReasonForTieringPolicyChange(changes, notSuppReasonBuff, exclude, "vPool");
return false;
}
return true;
}
/**
* For Auto-tiering policy change check, it logs the not supported reasons.
*
* @param changes Changes found
* @param notSuppReasonBuff Buffer containing current unsupported reasons
* @param exclude Checks that are excluded
* @param vPoolType The vpool type
*/
private static void logNotSupportedReasonForTieringPolicyChange(Map<String, Change> changes, StringBuffer notSuppReasonBuff,
String[] exclude, String vPoolType) {
notSuppReasonBuff.append(String.format("These target %s differences are invalid: ", vPoolType));
for (String key : changes.keySet()) {
s_logger.info("Unexpected Auto-tiering Policy {} attribute change: {}", vPoolType, key);
notSuppReasonBuff.append(key + " ");
}
s_logger.info("Virtual Pool change not supported {}", notSuppReasonBuff.toString());
s_logger.info(String.format("Parameters other than %s were changed",
Arrays.toString(exclude)));
}
/**
* Check that target vPool has volume's storage pool in its matched pools list.
* If target vPool has manual pool selection enabled, then volume's pool should be in assigned pools list.
*
* In case of VPLEX Distributed vPool, the check is also done for HA vPool.
*
* @param volume Volume involved in change vpool operation
* @param currentVpool The current volume's vpool
* @param newVpool The target vpool
* @param dbClient DBClient reference
* @return true if the target vpool has volume's storage pool in its matched pools list, false otherwise.
*/
private static boolean checkTargetVpoolHasVolumePool(Volume volume,
VirtualPool currentVpool, VirtualPool newVpool, DbClient dbClient) {
boolean vPoolHasVolumePool = false;
if (!NullColumnValueGetter.isNullURI(volume.getPool())) {
vPoolHasVolumePool = doesNewVpoolContainsVolumePool(volume.getPool(), newVpool);
} else if (VirtualPool.vPoolSpecifiesHighAvailability(currentVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(newVpool)) {
// check backend volume's pool with new vPool's pools
Volume backendSrcVolume = VPlexUtil.getVPLEXBackendVolume(volume, true, dbClient, false);
if (backendSrcVolume != null) {
s_logger.info("VPLEX backend Source Volume {}, new vPool {}", backendSrcVolume.getId(), newVpool.getId());
vPoolHasVolumePool = doesNewVpoolContainsVolumePool(backendSrcVolume.getPool(), newVpool);
} else {
s_logger.warn("backend source volume could not be found for VPLEX volume " + volume.forDisplay());
}
// check backend distributed volume's pool with new HA vPool's pools
if (VirtualPool.vPoolSpecifiesHighAvailabilityDistributed(currentVpool)
&& VirtualPool.vPoolSpecifiesHighAvailabilityDistributed(newVpool)) {
Volume backendDistVolume = VPlexUtil.getVPLEXBackendVolume(volume, false, dbClient, false);
VirtualPool newHAvPool = getHaVpool(newVpool, dbClient);
s_logger.info("VPLEX backend Distributed Volume {}, new HA vPool {}", backendDistVolume.getId(), newHAvPool.getId());
if (newHAvPool != null && backendDistVolume != null) {
vPoolHasVolumePool = doesNewVpoolContainsVolumePool(backendDistVolume.getPool(), newHAvPool);
}
}
}
return vPoolHasVolumePool;
}
/**
* Returns true if the vPool contains the given storage pool in its valid pools list.
*
* @param volumePool Volume's vpool
* @param vPool Vpool to check
* @return true if the vPool contains the given storage pool in its valid pools list, false otherwise
*/
private static boolean doesNewVpoolContainsVolumePool(URI volumePool, VirtualPool vPool) {
boolean vPoolHasVolumePool = false;
if (volumePool != null && vPool != null) {
StringSet poolsToCheck = vPool.getUseMatchedPools() ? vPool.getMatchedStoragePools() : vPool.getAssignedStoragePools();
if (poolsToCheck != null && poolsToCheck.contains(volumePool.toString())) {
vPoolHasVolumePool = true;
}
}
return vPoolHasVolumePool;
}
/**
* Gets the class variable indicating if the new vpool specified HA or not
*
* @return class variable indicating if the new vpool specified HA or not
*/
public static boolean getNewVpoolDoesNotSpecifyHaVpool() {
return newVpoolDoesNotSpecifyHaVpool;
}
/**
* Convenience method to add extra element to an array
*
* @param array reference to an array
* @param element new element to be added to the array
* @return the array update with new element
*/
private static String[] addElementToArray(String[] array, String element) {
Arrays.copyOf(array, array.length + 1);
array[array.length - 1] = element;
return array;
}
/**
* Determines if the volume qualifies for an upgrade to Metropoint.
*
* @param volume A reference to the volume.
* @param currentVpool A reference to the current volume Vpool.
* @param newVpool The desired new Vpool.
* @param dbClient A reference to a DB client.
* @param notSuppReasonBuff [OUT] Specifies the reason a Vpool change is not
* supported between the two Vpool.
* @return true if the upgrade to MP operation is allowed, false otherwise.
*/
public static boolean isSupportedUpgradeToMetroPointVirtualPoolChange(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
DbClient dbClient, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedUpgradeToMetroPointVirtualPoolChange from [%s] to [%s]...",
currentVpool.getLabel(), newVpool.getLabel()));
// Make sure the Vpool are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// RP+VPLEX Distributed to MetroPoint is supported
if (VirtualPool.vPoolSpecifiesRPVPlex(currentVpool)
&& VirtualPool.vPoolSpecifiesMetroPoint(newVpool)) {
if (null != volume.getAssociatedVolumes()
&& !volume.getAssociatedVolumes().isEmpty()
&& volume.getAssociatedVolumes().size() > 1) {
// Return false if any of the following properties are different
// between the current and new vpool.
String[] include = new String[] { TYPE, VARRAYS,
REF_VPOOL, MIRROR_VPOOL,
HIGH_AVAILABILITY,
FAST_EXPANSION, ACLS,
INACTIVE, HA_CONNECTED_TO_RP, JOURNAL_SIZE };
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, include, null, null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
if (currentVpool.getProtectionVarraySettings() == null
&& currentVpool.getProtectionVarraySettings().isEmpty()) {
notSuppReasonBuff.append(String.format("Vpool [%s] does not specify protection",
currentVpool.getLabel()));
return false;
}
if (newVpool.getProtectionVarraySettings() == null
&& newVpool.getProtectionVarraySettings().isEmpty()) {
notSuppReasonBuff.append(String.format("Vpool [%s] does not specify protection",
newVpool.getLabel()));
return false;
}
// Multiple existing targets not supported for now
// TODO BH: Maybe at some point we can consume existing targets and try to add
// copies as long as they line up. AND/OR support the newVpool having 1 - 3 targets
// as long as one target lines up and is CRR. Could get complex.
if (currentVpool.getProtectionVarraySettings().size() > 1
|| newVpool.getProtectionVarraySettings().size() > 1) {
notSuppReasonBuff.append("Multiple targets not supported for upgrade to MetroPoint (for now).");
return false;
} else {
// Check the Targets...
// Need to make sure that the new vpool has the same target varray/vpool
// defined to make the transition to MetroPoint seemless.
// Also applies to target journal varray/vpool.
for (Map.Entry<String, String> entry : newVpool.getProtectionVarraySettings().entrySet()) {
// Make sure they both use the same target varray
if (currentVpool.getProtectionVarraySettings().containsKey(entry.getKey())) {
// Now make sure they both use the same target vpool, this is pretty
// restrictive but our code path has to be precise for now.
String newSettingsId = entry.getValue();
String currentSettingsId = currentVpool.getProtectionVarraySettings().get(entry.getKey());
// Grab the current protection varray settings
VpoolProtectionVarraySettings currentProtectionVarraySetting = dbClient.queryObject(
VpoolProtectionVarraySettings.class,
URI.create(currentSettingsId));
// Grab the new protection varray settings
VpoolProtectionVarraySettings newProtectionVarraySetting = dbClient.queryObject(
VpoolProtectionVarraySettings.class,
URI.create(newSettingsId));
String currentTargetVpool = NullColumnValueGetter.getStringValue(currentProtectionVarraySetting
.getVirtualPool());
String currentTargetJournalVarray = NullColumnValueGetter.getStringValue(currentProtectionVarraySetting
.getJournalVarray());
String currentTargetJournalVpool = NullColumnValueGetter.getStringValue(currentProtectionVarraySetting
.getJournalVpool());
String newTargetVpool = NullColumnValueGetter.getStringValue(newProtectionVarraySetting.getVirtualPool());
String newTargetJournalVarray = NullColumnValueGetter.getStringValue(newProtectionVarraySetting
.getJournalVarray());
String newTargetJournalVpool = NullColumnValueGetter.getStringValue(newProtectionVarraySetting
.getJournalVpool());
// Make sure the target vpools are the same
if (!currentTargetVpool.equals(newTargetVpool)) {
notSuppReasonBuff.append("Target virtual pools do not match.");
return false;
}
// Make sure the target journal varrays are the same
if (!currentTargetJournalVarray.equals(newTargetJournalVarray)) {
notSuppReasonBuff.append("Target journal virtual arrays do not match.");
return false;
}
// Make sure the target journal vpools are the same
if (!currentTargetJournalVpool.equals(newTargetJournalVpool)) {
notSuppReasonBuff.append("Target journal virtual vpools do not match.");
return false;
}
} else {
notSuppReasonBuff.append("Target virtual arrays do not match.");
return false;
}
}
// Targets are OK if we reach here so on to the next check for Source/HA journals...
//
// The current RP+VPLEX vpool is either protecting the Source site or HA site.
// This will make a difference in checking which journal side should be added when
// upgrading to MetroPoint.
// If we were protecting the Source site, then the HA journal changes are OK and we need to check the Source Journal
// settings for changes.
// If we were protecting the HA site, then the Source journal changes are OK and we need to check the HA Journal
// settings for changes.
String currentJournalVarray = null;
String currentJournalVpool = null;
String newJournalVarray = null;
String newJournalVpool = null;
if (VirtualPool.isRPVPlexProtectHASide(currentVpool)) {
currentJournalVarray = NullColumnValueGetter.getStringValue(currentVpool.getStandbyJournalVarray());
currentJournalVpool = NullColumnValueGetter.getStringValue(currentVpool.getStandbyJournalVpool());
newJournalVarray = NullColumnValueGetter.getStringValue(newVpool.getStandbyJournalVarray());
newJournalVpool = NullColumnValueGetter.getStringValue(newVpool.getStandbyJournalVpool());
} else {
currentJournalVarray = NullColumnValueGetter.getStringValue(currentVpool.getJournalVarray());
currentJournalVpool = NullColumnValueGetter.getStringValue(currentVpool.getJournalVpool());
newJournalVarray = NullColumnValueGetter.getStringValue(newVpool.getJournalVarray());
newJournalVpool = NullColumnValueGetter.getStringValue(newVpool.getJournalVpool());
}
// Source journals need to match up
if (!currentJournalVarray.equals(newJournalVarray)) {
notSuppReasonBuff.append("Source journal virtual arrays do not match.");
return false;
}
if (!currentJournalVpool.equals(newJournalVpool)) {
notSuppReasonBuff.append("Source journal virtual pools do not match.");
return false;
}
}
} else {
// RP+VPLEX Local to MetroPoint
// Unsupported for now
notSuppReasonBuff.append("RP+VPLEX Local to MetroPoint change vpool unsupported for now.");
return false;
}
} else {
// Unsupported
notSuppReasonBuff.append("Upgrade to Metropoint operation is not supported.");
return false;
}
s_logger.info("Upgrade to Metropoint operation is supported.");
return true;
}
/**
* Checks to see if the remove protection operation is supported.
*
* @param volume A reference to the volume.
* @param currentVpool A reference to the current volume Vpool.
* @param newVpool The desired new Vpool.
* @param dbClient A reference to a DB client.
* @param notSuppReasonBuff Buffer for error messages
* @return true if remove protection is supported
*/
public static boolean isSupportedRPRemoveProtectionVirtualPoolChange(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
DbClient dbClient, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedRPRemoveProtectionVirtualPoolChange from [%s] to [%s]...",
currentVpool.getLabel(), newVpool.getLabel()));
// Make sure the Vpools are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
if (volume.checkForRp()
&& VirtualPool.vPoolSpecifiesProtection(currentVpool)
&& !VirtualPool.vPoolSpecifiesProtection(newVpool)) {
// Check that nothing other than the excluded attributes changed.
List<String> excluded = new ArrayList<String>();
String[] exclude = new String[] { PROTECTION_VARRAY_SETTINGS, RP_RPO_VALUE, RP_RPO_TYPE,
RP_COPY_MODE, ARRAY_INFO, DRIVE_TYPE, JOURNAL_SIZE, JOURNAL_VARRAY, JOURNAL_VPOOL,
MULTI_VOLUME_CONSISTENCY, METROPOINT, STANDBY_JOURNAL_VARRAY, STANDBY_JOURNAL_VPOOL };
excluded.addAll(Arrays.asList(exclude));
excluded.addAll(Arrays.asList(GENERALLY_EXCLUDED));
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, null, excluded.toArray(exclude), null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append("These target virtual pool differences are invalid: ");
fillInNotSupportedReasons(changes, notSuppReasonBuff);
s_logger.info("Remove Protection change not supported, "
+ "these target pool differences are invalid: {}", notSuppReasonBuff.toString());
return false;
}
} else {
s_logger.warn("RP remove protection operation is NOT supported.");
return false;
}
s_logger.info("RP remove protection operation is supported.");
return true;
}
/**
* Checks to see if the file replication change is supported.
*
* @param currentVpool the source virtual pool
* @param newVpool the target virtual pool
* @param notSuppReasonBuff the not supported reason string buffer
* @return true if file replication change is supported
*/
public static boolean isSupportedFileReplicationChange(VirtualPool currentVpool, VirtualPool newVpool, StringBuffer notSuppReasonBuff) {
s_logger.info(String.format("Checking isSupportedFileReplicationChange from [%s] to [%s]...", currentVpool.getLabel(),
newVpool.getLabel()));
// Make sure the VirtualPool's are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
if (!newVpool.getVirtualArrays().containsAll(currentVpool.getVirtualArrays())) {
notSuppReasonBuff
.append(String
.format("Virtual pool change not supported, Because Target Varray is different %s.",
newVpool.getLabel()));
s_logger.info(notSuppReasonBuff.toString());
return false;
}
// Check that nothing other than the excluded attributes changed.
List<String> excluded = new ArrayList<String>();
String[] exclude = new String[] { FILE_REPLICATION_TYPE, FILE_REPLICATION_COPY_MODE,
FILE_REPLICATION_RPO_TYPE, FILE_REPLICATION_RPO_VALUE, FILE_REPLICATION_COPIES, VARRAYS, FILE_REPLICATION_AT_PROJECT_LEVEL,
FILE_REPLICATION_AT_FS_LEVEL, FILE_REPLICATION_SUPPORTED, FILE_SNAPSHOT_SCHEDULE_SUPPORTED };
excluded.addAll(Arrays.asList(exclude));
excluded.addAll(Arrays.asList(GENERALLY_EXCLUDED));
Map<String, Change> changes = analyzeChanges(currentVpool, newVpool, null, excluded.toArray(exclude), null);
if (!changes.isEmpty()) {
notSuppReasonBuff.append(String.format("These target virtual pool [%s] differences are invalid: ", newVpool.getLabel()));
fillInNotSupportedReasons(changes, notSuppReasonBuff);
s_logger.info(String.format("Virtual pool change not supported. %s. Parameters other than %s were changed.",
notSuppReasonBuff.toString(), excluded.toString()));
return false;
}
return true;
}
/**
* From the changes map passed in, assemble all the changes into a comma delimited format.
*
* @param changes A map with all the changes found that are allowed/not allowed
* @param notSuppReasonBuff Buffer with all the change messages appended.
*/
private static void fillInNotSupportedReasons(Map<String, Change> changes, StringBuffer notSuppReasonBuff) {
Set<String> allChanges = new HashSet<String>();
for (Change foundChange : changes.values()) {
// Use the plain name field from the change object
allChanges.add(foundChange.name);
}
notSuppReasonBuff.append(Joiner.on(", ").join(allChanges));
notSuppReasonBuff.append(". ");
}
/**
* Checks to see if the migration operation is supported for RP+VPLEX or MetroPoint.
*
* @param volume A reference to the volume.
* @param currentVpool A reference to the current volume Vpool.
* @param newVpool The desired new Vpool.
* @param dbClient A reference to a DB client.
* @param notSuppReasonBuff Buffer for error messages
* @return true if remove protection is supported
*/
public static boolean isSupportedRPVPlexMigrationVirtualPoolChange(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
DbClient dbClient, StringBuffer notSuppReasonBuff, List<RPVPlexMigration> validMigrations) {
s_logger.info(String.format("Checking isSupportedRPVPlexMigrationVirtualPoolChange from [%s] to [%s]...",
currentVpool.getLabel(), newVpool.getLabel()));
// Make sure the VirtualPool's are not the same instance.
if (isSameVirtualPool(currentVpool, newVpool, notSuppReasonBuff)) {
return false;
}
// Flag to indicate that at least one valid migration has been found
boolean validMigrationsFound = false;
if (volume.checkForRp()
&& VirtualPool.vPoolSpecifiesRPVPlex(currentVpool)
&& VirtualPool.vPoolSpecifiesRPVPlex(newVpool)) {
// Number of targets can not be different between the current and new vpool.
if (currentVpool.getProtectionVarraySettings().size() != newVpool.getProtectionVarraySettings().size()) {
notSuppReasonBuff.append("Target virtual arrays do not match.");
return false;
}
// Keep track of all the potential migrations
List<RPVPlexMigration> potentialMigrations = new ArrayList<RPVPlexMigration>();
// The Source is always a potential candidate for migration
potentialMigrations.add(new RPVPlexMigration(Volume.PersonalityTypes.SOURCE,
volume.getVirtualArray(), currentVpool, newVpool));
// Source Journal
boolean invalidMigration = determineRPSourceJournalMigration(volume, currentVpool, newVpool, potentialMigrations,
notSuppReasonBuff, dbClient);
if (invalidMigration) {
return false;
}
// Only MetroPoint configurations will have Standby Journals
if (VirtualPool.vPoolSpecifiesMetroPoint(currentVpool)
&& VirtualPool.vPoolSpecifiesMetroPoint(newVpool)) {
// Standby Journal
invalidMigration = determineRPStandbyJournalMigration(volume, currentVpool, newVpool, potentialMigrations,
notSuppReasonBuff, dbClient);
if (invalidMigration) {
return false;
}
}
// Targets
invalidMigration = determineRPTargetMigration(volume, currentVpool, newVpool, potentialMigrations,
notSuppReasonBuff, dbClient);
if (invalidMigration) {
return false;
}
// Iterate over all the potential for migrations (SOURCE, TARGET, METADATA).
for (RPVPlexMigration migration : potentialMigrations) {
Volume.PersonalityTypes type = migration.getType();
VirtualPool candidateCurrentVpool = migration.getMigrateFromVpool();
VirtualPool candidateNewVpool = migration.getMigrateToVpool();
// Same vpool automatically excludes this entry
if (isSameVirtualPool(candidateCurrentVpool, candidateNewVpool, notSuppReasonBuff)) {
continue;
}
String[] include = null;
if (type.equals(Volume.PersonalityTypes.SOURCE)) {
// Ensure these values have NOT changed between ANY of the Source candidate vpools. If they have,
// do not allow the RP+VPLEX migration operation to occur.
include = new String[] { TYPE, VARRAYS,
REF_VPOOL, MIRROR_VPOOL, FAST_EXPANSION, ACLS, INACTIVE, NUM_PATHS,
METROPOINT, HIGH_AVAILABILITY, RP_RPO_TYPE, RP_RPO_VALUE, RP_COPY_MODE };
} else {
// Targets/Journals could be using the Source/Target vpool as their vpool
// and the user could be looking to migrate to another vpool which is valid
// but may have different a HA type / varrays.
if (VirtualPool.vPoolSpecifiesHighAvailability(candidateCurrentVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(candidateNewVpool)
&& candidateNewVpool.getVirtualArrays().contains(migration.getVarray().toString())) {
include = new String[] { TYPE, REF_VPOOL, MIRROR_VPOOL, FAST_EXPANSION, ACLS, INACTIVE, NUM_PATHS };
} else {
// Can not consider this migration if the current and new vpool
// does not specify HA and the new vpool does not have the varray used in
// the migration.
s_logger.info(String.format("Vpool [%s](%s) is NOT valid for RP+VPLEX %s migrations",
candidateNewVpool.getLabel(),
candidateNewVpool.getId()), type.name());
continue;
}
}
// Ensure no unwanted changes are present.
Map<String, Change> changes = analyzeChanges(candidateCurrentVpool, candidateNewVpool, include, null, null);
if (!changes.isEmpty()) {
notSuppReasonBuff
.append(String.format("Changes in the following %s virtual pool are not permitted: ", type.name()));
fillInNotSupportedReasons(changes, notSuppReasonBuff);
return false;
}
// Determine if VPLEX source side will be migrated.
boolean migrateSourceVolume = VirtualPoolChangeAnalyzer
.vpoolChangeRequiresMigration(candidateCurrentVpool, candidateNewVpool);
// Determine if VPLEX HA side will be migrated.
boolean migrateHAVolume = false;
// Ignore HA for Journals as ViPR provisioned RP+VPLEX Journals are always forced to VPLEX Local.
if (!type.equals(Volume.PersonalityTypes.METADATA)) {
VirtualPool candidateCurrentHaVpool = VirtualPoolChangeAnalyzer
.getHaVpool(candidateCurrentVpool, dbClient);
if (candidateCurrentHaVpool != null) {
VirtualPool candidateNewHaVpool = VirtualPoolChangeAnalyzer
.getNewHaVpool(candidateCurrentVpool, candidateNewVpool, dbClient);
migrateHAVolume = VirtualPoolChangeAnalyzer
.vpoolChangeRequiresMigration(candidateCurrentHaVpool, candidateNewHaVpool);
}
}
if (migrateSourceVolume || migrateHAVolume) {
// There is at least one migration candidate, so we can proceed.
validMigrationsFound = true;
s_logger.info(String.format("Vpool [%s](%s) is valid for RP+VPLEX %s migrations",
candidateNewVpool.getLabel(),
candidateNewVpool.getId(),
type.name()));
// If the validMigrations param is not null then keep track of the valid
// migrations found.
if (validMigrations != null) {
validMigrations.add(migration);
} else {
break;
}
} else {
s_logger.info(String.format("Vpool [%s](%s) is NOT valid for RP+VPLEX %s migrations",
candidateNewVpool.getLabel(),
candidateNewVpool.getId(),
type.name()));
}
}
}
s_logger.info(String.format("RP+VPLEX migration operation is%s supported.",
validMigrationsFound ? "" : " NOT"));
return validMigrationsFound;
}
/**
* Determines if there are any Source Journal migrations and adds them to the
* potentialMigrations container.
*
* @param volume The change vpool volume
* @param currentVpool The change vpool volume's current vpool
* @param newVpool The target vpool to move to
* @param potentialMigrations Container for migrations
* @param notSuppReasonBuff Buffer for not supported reasons
* @param dbClient DbClient instance
* @return true if invalid migration, false otherwise
*/
private static boolean determineRPSourceJournalMigration(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
List<RPVPlexMigration> potentialMigrations, StringBuffer notSuppReasonBuff, DbClient dbClient) {
boolean invalidMigration = false;
// Current Source Journal varray/vpool
String currentSourceJournalVarrayId = NullColumnValueGetter.getStringValue(currentVpool
.getJournalVarray());
String currentSourceJournalVpoolId = NullColumnValueGetter.getStringValue(currentVpool
.getJournalVpool());
// New Source Journal varray/vpool
String newSourceJournalVarrayId = NullColumnValueGetter.getStringValue(newVpool
.getJournalVarray());
String newSourceJournalVpoolId = NullColumnValueGetter.getStringValue(newVpool
.getJournalVpool());
// If the current and new Source Journal varray/vpool are not set, default them to known values.
if (currentSourceJournalVarrayId.equals(NullColumnValueGetter.getNullStr())) {
currentSourceJournalVarrayId = volume.getVirtualArray().toString();
}
if (currentSourceJournalVpoolId.equals(NullColumnValueGetter.getNullStr())) {
currentSourceJournalVpoolId = currentVpool.getId().toString();
}
VirtualPool currentSourceJournalVpool = dbClient.queryObject(VirtualPool.class, URI.create(currentSourceJournalVpoolId));
if (newSourceJournalVpoolId.equals(NullColumnValueGetter.getNullStr())) {
newSourceJournalVpoolId = newVpool.getId().toString();
}
VirtualPool newSourceJournalVpool = dbClient.queryObject(VirtualPool.class, URI.create(newSourceJournalVpoolId));
if (newSourceJournalVarrayId.equals(NullColumnValueGetter.getNullStr())) {
if (newSourceJournalVpool.getVirtualArrays() != null
&& newSourceJournalVpool.getVirtualArrays().contains(volume.getVirtualArray().toString())) {
newSourceJournalVarrayId = volume.getVirtualArray().toString();
}
}
// Only consider the Source Journal migration if the varrays are the same and the vpools
// are different and both specify HA.
if (!currentSourceJournalVpoolId.equals(newSourceJournalVpoolId)) {
if (currentSourceJournalVarrayId.equals(newSourceJournalVarrayId)
&& VirtualPool.vPoolSpecifiesHighAvailability(currentSourceJournalVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(newSourceJournalVpool)) {
// Add Source Journal for potential migration
potentialMigrations.add(new RPVPlexMigration(Volume.PersonalityTypes.METADATA, Volume.PersonalityTypes.SOURCE,
URI.create(currentSourceJournalVarrayId), currentSourceJournalVpool, newSourceJournalVpool));
} else {
// If the Source Journal vpools are different and both of the Source Journal vpools do not specify HA,
// then exclude this new vpool for RP+VPLEX migration.
notSuppReasonBuff.append("Not valid for migration due to changes in RP Source Journal virtual pool / virtual array.");
invalidMigration = true;
}
} else {
if (!currentSourceJournalVarrayId.equals(newSourceJournalVarrayId)) {
notSuppReasonBuff.append("Not valid for migration due to changes in RP Source Journal virtual array.");
invalidMigration = true;
}
}
return invalidMigration;
}
/**
* Determines if there are any Standby Journal migrations and adds them to the
* potentialMigrations container.
*
* @param volume The change vpool volume
* @param currentVpool The change vpool volume's current vpool
* @param newVpool The target vpool to move to
* @param potentialMigrations Container for migrations
* @param notSuppReasonBuff Buffer for not supported reasons
* @param dbClient DbClient instance
* @return true if invalid migration, false otherwise
*/
private static boolean determineRPStandbyJournalMigration(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
List<RPVPlexMigration> potentialMigrations, StringBuffer notSuppReasonBuff, DbClient dbClient) {
boolean invalidMigration = false;
// Current Standby Journal varray/vpool
String currentStandbyJournalVarrayId = NullColumnValueGetter.getStringValue(currentVpool
.getStandbyJournalVarray());
String currentStandbyJournalVpoolId = NullColumnValueGetter.getStringValue(currentVpool
.getStandbyJournalVpool());
// New Standby Journal varray/vpool
String newStandbyJournalVarrayId = NullColumnValueGetter.getStringValue(newVpool
.getStandbyJournalVarray());
String newStandbyJournalVpoolId = NullColumnValueGetter.getStringValue(newVpool
.getStandbyJournalVpool());
// If the current Standby Journal varray/vpool are not set, default them to known values.
if (currentStandbyJournalVarrayId.equals(NullColumnValueGetter.getNullStr())) {
URI haVarrayURI = getHaVarrayURI(currentVpool);
currentStandbyJournalVarrayId = (haVarrayURI != null ? haVarrayURI.toString() : NullColumnValueGetter.getNullStr());
}
if (currentStandbyJournalVpoolId.equals(NullColumnValueGetter.getNullStr())) {
VirtualPool currentHaVpool = VirtualPool.getHAVPool(currentVpool, dbClient);
currentStandbyJournalVpoolId = (currentHaVpool != null ? currentHaVpool.getId().toString() : currentVpool.getId().toString());
}
VirtualPool currentStandbyJournalVpool = dbClient.queryObject(VirtualPool.class, URI.create(currentStandbyJournalVpoolId));
if (newStandbyJournalVarrayId.equals(NullColumnValueGetter.getNullStr())) {
URI haVarrayURI = getHaVarrayURI(newVpool);
newStandbyJournalVarrayId = (haVarrayURI != null ? haVarrayURI.toString() : NullColumnValueGetter.getNullStr());
}
if (newStandbyJournalVpoolId.equals(NullColumnValueGetter.getNullStr())) {
VirtualPool newHaVpool = VirtualPool.getHAVPool(newVpool, dbClient);
newStandbyJournalVpoolId = (newHaVpool != null ? newHaVpool.getId().toString() : newVpool.getId().toString());
}
VirtualPool newStandbyJournalVpool = dbClient.queryObject(VirtualPool.class, URI.create(newStandbyJournalVpoolId));
// Only consider the Standby Journal migration if the varrays are the same and the vpools
// are different and both specify HA.
if (!currentStandbyJournalVpoolId.equals(newStandbyJournalVpoolId)) {
if (currentStandbyJournalVarrayId.equals(newStandbyJournalVarrayId)
&& VirtualPool.vPoolSpecifiesHighAvailability(currentStandbyJournalVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(newStandbyJournalVpool)) {
// Add Standby Journal for potential migration
potentialMigrations.add(new RPVPlexMigration(Volume.PersonalityTypes.METADATA, Volume.PersonalityTypes.SOURCE,
URI.create(currentStandbyJournalVarrayId), currentStandbyJournalVpool, newStandbyJournalVpool));
} else {
// If the Standby Journal vpools are different and both of the Standby Journal vpools do not specify HA,
// then exclude this new vpool for RP+VPLEX migration.
notSuppReasonBuff.append("Not valid for migration due to changes in RP Standby Journal virtual pool / virtual array.");
invalidMigration = true;
}
} else {
if (!currentStandbyJournalVarrayId.equals(newStandbyJournalVarrayId)) {
notSuppReasonBuff.append("Not valid for migration due to changes in RP Standby Journal virtual array.");
invalidMigration = true;
}
}
return invalidMigration;
}
/**
* Determines if there are any Target migrations and adds them to the
* potentialMigrations container.
*
* @param volume The change vpool volume
* @param currentVpool The change vpool volume's current vpool
* @param newVpool The target vpool to move to
* @param potentialMigrations Container for migrations
* @param notSuppReasonBuff Buffer for not supported reasons
* @param dbClient DbClient instance
* @return true if invalid migration, false otherwise
*/
private static boolean determineRPTargetMigration(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
List<RPVPlexMigration> potentialMigrations, StringBuffer notSuppReasonBuff, DbClient dbClient) {
boolean invalidMigration = false;
if (currentVpool.getProtectionVarraySettings() == null
|| newVpool.getProtectionVarraySettings() == null
|| (currentVpool.getProtectionVarraySettings().size() != newVpool.getProtectionVarraySettings().size())) {
notSuppReasonBuff.append("RP Targets are mismatched.");
return true;
}
// Check the Targets for potential candidates for migration
for (Map.Entry<String, String> entry : currentVpool.getProtectionVarraySettings().entrySet()) {
String targetVarrayId = entry.getKey();
// Make sure we find the same varray in the new vpool, otherwise we can immediately
// exclude the migration operation.
if (newVpool.getProtectionVarraySettings().containsKey(targetVarrayId)) {
String currentProtectionVarraySettingsId = entry.getValue();
String newProtectionVarraySettingsId = newVpool.getProtectionVarraySettings().get(targetVarrayId);
// Get the current protection varray settings
VpoolProtectionVarraySettings currentProtectionVarraySetting = dbClient.queryObject(VpoolProtectionVarraySettings.class,
URI.create(currentProtectionVarraySettingsId));
// Get the new protection varray settings
VpoolProtectionVarraySettings newProtectionVarraySetting = dbClient.queryObject(VpoolProtectionVarraySettings.class,
URI.create(newProtectionVarraySettingsId));
// Current Target vpool
String currentTargetVpoolId = NullColumnValueGetter.getStringValue(currentProtectionVarraySetting
.getVirtualPool());
if (currentTargetVpoolId.equals(NullColumnValueGetter.getNullStr())) {
currentTargetVpoolId = currentVpool.getId().toString();
}
VirtualPool currentTargetVpool = dbClient.queryObject(VirtualPool.class, URI.create(currentTargetVpoolId));
// New Target vpool
String newTargetVpoolId = NullColumnValueGetter.getStringValue(newProtectionVarraySetting.getVirtualPool());
if (newTargetVpoolId.equals(NullColumnValueGetter.getNullStr())) {
newTargetVpoolId = newVpool.getId().toString();
}
VirtualPool newTargetVpool = dbClient.queryObject(VirtualPool.class, URI.create(newTargetVpoolId));
// Only allow migrations when both vpools specify HA.
if (!currentTargetVpoolId.equals(newTargetVpoolId)) {
if (VirtualPool.vPoolSpecifiesHighAvailability(currentTargetVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(newTargetVpool)) {
// Add Target for potential migration
potentialMigrations.add(new RPVPlexMigration(Volume.PersonalityTypes.TARGET,
URI.create(targetVarrayId), currentTargetVpool, newTargetVpool));
} else {
// This Target is not a candidate for migration and the vpools are not the exact
// same so we can not allow the migration. The Targets could potentially get misaligned
// in the new vpool and we can not allow that.
notSuppReasonBuff.append("No RP Target migration detected, so RP Target virtual pools must match.");
invalidMigration = true;
break;
}
}
// Target Journal
invalidMigration = determineRPTargetJournalMigration(volume, currentTargetVpool, newTargetVpool, potentialMigrations,
notSuppReasonBuff, dbClient, currentProtectionVarraySetting, newProtectionVarraySetting, targetVarrayId);
if (invalidMigration) {
break;
}
} else {
notSuppReasonBuff.append("Target virtual arrays do not match.");
invalidMigration = true;
break;
}
}
return invalidMigration;
}
/**
* Determines if there are any Target Journal migrations and adds them to the
* potentialMigrations container.
*
* @param volume The change vpool volume
* @param currentVpool The change vpool volume's current vpool
* @param newVpool The target vpool to move to
* @param potentialMigrations Container for migrations
* @param notSuppReasonBuff Buffer for not supported reasons
* @param dbClient DbClient instance
* @param currentProtectionVarraySetting Current vpool protection settings
* @param newProtectionVarraySetting New vpool protection settings
* @param targetVarrayId Current target varray ID
* @return true if invalid migration, false otherwise
*/
private static boolean determineRPTargetJournalMigration(Volume volume, VirtualPool currentVpool, VirtualPool newVpool,
List<RPVPlexMigration> potentialMigrations, StringBuffer notSuppReasonBuff, DbClient dbClient,
VpoolProtectionVarraySettings currentProtectionVarraySetting, VpoolProtectionVarraySettings newProtectionVarraySetting,
String targetVarrayId) {
boolean invalidMigration = false;
// Current Target Journal varray/vpool
String currentTargetJournalVarrayId = NullColumnValueGetter.getStringValue(currentProtectionVarraySetting
.getJournalVarray());
String currentTargetJournalVpoolId = NullColumnValueGetter.getStringValue(currentProtectionVarraySetting
.getJournalVpool());
// New Target Journal varray/vpool
String newTargetJournalVarrayId = NullColumnValueGetter.getStringValue(newProtectionVarraySetting
.getJournalVarray());
String newTargetJournalVpoolId = NullColumnValueGetter.getStringValue(newProtectionVarraySetting
.getJournalVpool());
// If the current and new Target Journal varray/vpool are not set, default them to known values.
if (currentTargetJournalVarrayId.equals(NullColumnValueGetter.getNullStr())) {
currentTargetJournalVarrayId = targetVarrayId;
}
if (currentTargetJournalVpoolId.equals(NullColumnValueGetter.getNullStr())) {
currentTargetJournalVpoolId = currentVpool.getId().toString();
}
VirtualPool currentTargetJournalVpool = dbClient.queryObject(VirtualPool.class, URI.create(currentTargetJournalVpoolId));
if (newTargetJournalVpoolId.equals(NullColumnValueGetter.getNullStr())) {
newTargetJournalVpoolId = newVpool.getId().toString();
}
VirtualPool newTargetJournalVpool = dbClient.queryObject(VirtualPool.class, URI.create(newTargetJournalVpoolId));
if (newTargetJournalVarrayId.equals(NullColumnValueGetter.getNullStr())) {
if (newTargetJournalVpool.getVirtualArrays() != null
&& newTargetJournalVpool.getVirtualArrays().contains(targetVarrayId)) {
newTargetJournalVarrayId = targetVarrayId;
}
}
// Only consider the Target Journal migration if the varrays are the same and the vpools
// are different and both specify HA.
if (!currentTargetJournalVpoolId.equals(newTargetJournalVpoolId)) {
if (currentTargetJournalVarrayId.equals(newTargetJournalVarrayId)
&& VirtualPool.vPoolSpecifiesHighAvailability(currentTargetJournalVpool)
&& VirtualPool.vPoolSpecifiesHighAvailability(newTargetJournalVpool)) {
// Add Target Journal for potential migration
potentialMigrations.add(new RPVPlexMigration(Volume.PersonalityTypes.METADATA, Volume.PersonalityTypes.TARGET,
URI.create(currentTargetJournalVarrayId), currentTargetJournalVpool, newTargetJournalVpool));
} else {
// If the Target Journal vpools are different and both of the Target Journal vpools do not specify HA,
// then exclude this new vpool for RP+VPLEX migration.
notSuppReasonBuff.append("Not valid for migration due to changes in RP Target Journal virtual pool / virtual array.");
invalidMigration = true;
}
} else {
if (!currentTargetJournalVarrayId.equals(newTargetJournalVarrayId)) {
notSuppReasonBuff.append("Not valid for migration due to changes in RP Target Journal virtual array.");
invalidMigration = true;
}
}
return invalidMigration;
}
}