/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource.utils.vpoolvalidators;
import static com.google.common.base.Predicates.and;
import static com.google.common.collect.Maps.filterEntries;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.emc.storageos.api.service.impl.resource.ArgValidator;
import com.emc.storageos.api.service.impl.resource.utils.VirtualPoolValidator;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.model.vpool.BlockVirtualPoolParam;
import com.emc.storageos.model.vpool.BlockVirtualPoolProtectionParam;
import com.emc.storageos.model.vpool.BlockVirtualPoolProtectionUpdateParam;
import com.emc.storageos.model.vpool.BlockVirtualPoolUpdateParam;
import com.emc.storageos.model.vpool.ProtectionSourcePolicy;
import com.emc.storageos.model.vpool.VirtualPoolProtectionRPChanges;
import com.emc.storageos.model.vpool.VirtualPoolProtectionRPParam;
import com.emc.storageos.model.vpool.VirtualPoolProtectionVirtualArraySettingsParam;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.google.common.base.Predicate;
public class ProtectionValidator extends VirtualPoolValidator<BlockVirtualPoolParam, BlockVirtualPoolUpdateParam> {
private static final String HIGH_AVAILABILITY = "highAvailability";
private static final String HIGH_AVAILABILITY_NONE = "NONE";
private static final String PROTECTION_RP = "protection.rp";
private static final String PROTECTION_MIRRORS = "protection.mirrors";
private static final String PROTECTION_SNAPSHOTS = "protection.snapshots";
private static final String JOURNAL_REGEX_1 = "[0-9]+(GB|TB|MB)";
private static final String JOURNAL_REGEX_2 = "[0-9]+(\\.[0-9]+)?[xX]";
private static final String JOURNAL_MIN = "min";
/**
* Add known protections to the returned Map instance.
*
* @param createParam VirtualPool CreateParam instance
* @return A Map containing known protection parameters
*/
private Map<String, Object> getProtectionParameters(BlockVirtualPoolParam createParam) {
Map<String, Object> protections = new HashMap<String, Object>();
if (createParam.getHighAvailability() != null) {
protections.put(HIGH_AVAILABILITY, createParam.getHighAvailability().getType());
}
if (createParam.getProtection() != null) {
if (createParam.getProtection().getRecoverPoint() != null) {
protections.put(PROTECTION_RP, createParam.getProtection().getRecoverPoint());
}
if (createParam.getProtection().getContinuousCopies() != null &&
createParam.getProtection().getContinuousCopies().getMaxMirrors() != null &&
createParam.getProtection().getContinuousCopies().getMaxMirrors() != VirtualPool.MAX_DISABLED) {
protections.put(PROTECTION_MIRRORS, createParam.getProtection().getContinuousCopies().getMaxMirrors());
}
if (createParam.getProtection().getSnapshots() != null &&
createParam.getProtection().getSnapshots().getMaxSnapshots() != null &&
createParam.getProtection().getSnapshots().getMaxSnapshots() != VirtualPool.MAX_DISABLED) {
protections.put(PROTECTION_SNAPSHOTS, createParam.getProtection().getSnapshots().getMaxSnapshots());
}
}
return protections;
}
/**
* Add known protection updates to the returned Map instance.
*
* @param updateParam VirtualPoolUpdateParam instance
* @return A Map containing known protection update parameters
*/
private Map<String, Object> getProtectionUpdateParameters(BlockVirtualPoolUpdateParam updateParam) {
Map<String, Object> protections = new HashMap<String, Object>();
if (updateParam.getHighAvailability() != null) {
protections.put(HIGH_AVAILABILITY, updateParam.getHighAvailability().getType());
}
if (updateParam.getProtection() != null) {
if (updateParam.getProtection().getRecoverPoint() != null) {
protections.put(PROTECTION_RP, updateParam.getProtection().getRecoverPoint());
}
if (updateParam.getProtection().getContinuousCopies() != null &&
updateParam.getProtection().getContinuousCopies().getMaxMirrors() != null &&
updateParam.getProtection().getContinuousCopies().getMaxMirrors() != VirtualPool.MAX_DISABLED) {
protections.put(PROTECTION_MIRRORS, updateParam.getProtection().getContinuousCopies().getMaxMirrors());
}
if (updateParam.getProtection().getSnapshots() != null &&
updateParam.getProtection().getSnapshots().getMaxSnapshots() != null &&
updateParam.getProtection().getSnapshots().getMaxSnapshots() != VirtualPool.MAX_DISABLED) {
protections.put(PROTECTION_SNAPSHOTS, updateParam.getProtection().getSnapshots().getMaxSnapshots());
}
}
return protections;
}
@Override
public void setNextValidator(VirtualPoolValidator validator) {
_nextValidator = validator;
}
@Override
protected void validateVirtualPoolCreateAttributeValue(BlockVirtualPoolParam createParam, DbClient dbClient) {
validateNoExpansionWithMirroringOrFail(createParam);
validateJournalSizes(createParam, dbClient);
validateMultiVolumeConsistency(createParam);
validateRpNonHaCopyVpools(createParam, dbClient);
validateMetroPoint(createParam, dbClient);
}
@Override
protected boolean isCreateAttributeOn(BlockVirtualPoolParam createParam) {
return (createParam.getProtection() != null);
}
@Override
protected void validateVirtualPoolUpdateAttributeValue(VirtualPool virtualPool, BlockVirtualPoolUpdateParam updateParam,
DbClient dbClient) {
validateNoExpansionWithMirroringOrFail(virtualPool, updateParam);
validateJournalSizes(virtualPool, updateParam, dbClient);
validateRpNonHaCopyVpools(updateParam, dbClient);
validateMultiVolumeConsistency(virtualPool);
validateMetroPoint(virtualPool);
}
@Override
protected boolean isUpdateAttributeOn(BlockVirtualPoolUpdateParam updateParam) {
// must also check high availability due to its validation relationship with continuous copy count
return (updateParam.getProtection() != null || updateParam.getHighAvailability() != null);
}
/**
* Validation of journal sizes and other protection parameters, if they exist
*
* @param createParam creation parameters
* @param dbClient db client
*/
private void validateJournalSizes(BlockVirtualPoolParam createParam, DbClient dbClient) {
Map<String, Object> protection = getProtectionParameters(createParam);
Map<String, Object> enabledProtections = filterEntries(protection,
and(paramEntryValueNotNull(), paramEntryValueNotNone()));
_logger.info("Requested VirtualPool protections: {}", enabledProtections);
if (enabledProtections.get(PROTECTION_RP) != null) {
VirtualPoolProtectionRPParam rp = (VirtualPoolProtectionRPParam) enabledProtections.get(PROTECTION_RP);
if (rp != null) {
if (rp.getCopies() != null) {
validateSourcePolicy(rp.getSourcePolicy());
validateProtectionCopies(rp.getCopies(), dbClient);
} else if (rp.getSourcePolicy() != null && rp.getSourcePolicy().getJournalSize() != null) {
throwServicePolicyNoProtectionException();
}
}
}
}
/**
* Validation of journal sizes and other protection parameters, if they exist.
*
* @param virtualPool
* @param updateParam
* @param dbClient
*/
private void validateJournalSizes(VirtualPool virtualPool,
BlockVirtualPoolUpdateParam updateParam, DbClient dbClient) {
Map<String, Object> protection = getProtectionUpdateParameters(updateParam);
Map<String, Object> enabledProtections = filterEntries(protection,
and(paramEntryValueNotNull(), paramEntryValueNotNone()));
_logger.info("Requested VirtualPool protections: {}", enabledProtections);
if (enabledProtections.get(PROTECTION_RP) != null) {
VirtualPoolProtectionRPChanges rp = (VirtualPoolProtectionRPChanges) enabledProtections.get(PROTECTION_RP);
if (rp != null) {
// The virtual pool must specify RP protection or RP protection
// must be specified in the update request.
if (VirtualPool.vPoolSpecifiesProtection(virtualPool) || (rp.getAdd() != null && !rp.getAdd().isEmpty())) {
validateSourcePolicy(rp.getSourcePolicy());
if (rp.getAdd() != null) {
validateProtectionCopies(rp.getAdd(), dbClient);
}
} else if (rp.getSourcePolicy() != null && rp.getSourcePolicy().getJournalSize() != null) {
throwServicePolicyNoProtectionException();
}
}
}
}
/**
* Validates the source policy to ensure valid values are specified.
*
* @param sourcePolicy The policy to validate
*/
private void validateSourcePolicy(ProtectionSourcePolicy sourcePolicy) {
// Validate source policy settings
if (sourcePolicy != null) {
if (sourcePolicy.getJournalSize() != null) {
if (!isParsableToDouble(sourcePolicy.getJournalSize()) && !sourcePolicy.getJournalSize()
.matches(JOURNAL_REGEX_1) && !sourcePolicy.getJournalSize()
.matches(JOURNAL_REGEX_2) && !sourcePolicy.getJournalSize()
.equals(JOURNAL_MIN)) {
throw APIException.badRequests.protectionVirtualPoolJournalSizeInvalid("source", sourcePolicy.getJournalSize());
}
}
if (sourcePolicy.getRemoteCopyMode() != null) {
if (VirtualPool.RPCopyMode.lookup(sourcePolicy.getRemoteCopyMode()) == null) {
throw APIException.badRequests.protectionVirtualPoolRemoteCopyModeInvalid(sourcePolicy.getRemoteCopyMode());
}
}
if (sourcePolicy.getRpoType() != null) {
if (VirtualPool.RPOType.lookup(sourcePolicy.getRpoType()) == null) {
throw APIException.badRequests.protectionVirtualPoolRPOTypeInvalid(sourcePolicy.getRpoType());
}
}
if (sourcePolicy.getRpoValue() != null && sourcePolicy.getRpoType() == null) {
throw APIException.badRequests.protectionVirtualPoolRPOTypeNotSpecified(sourcePolicy.getRpoValue());
}
if (sourcePolicy.getRpoValue() == null && sourcePolicy.getRpoType() != null) {
throw APIException.badRequests.protectionVirtualPoolRPOValueNotSpecified(sourcePolicy.getRpoType());
}
}
}
/**
* Validates the RP protection copies. The protection copy consists of
* a virtual array, virtual pool, and copy policy - each of which are
* validated if they are specified.
*
* @param copies The protection copies
* @param dbClient The dbclient
*/
private void validateProtectionCopies(Set<VirtualPoolProtectionVirtualArraySettingsParam> copies, DbClient dbClient) {
if (copies != null) {
for (VirtualPoolProtectionVirtualArraySettingsParam settingsParam : copies) {
// Validate the protection copy virtual array. This is a required filed when adding
// a protection copy.
if (settingsParam.getVarray() != null && !settingsParam.getVarray().toString().isEmpty()) {
ArgValidator.checkUri(settingsParam.getVarray());
VirtualArray neighborhood = dbClient.queryObject(VirtualArray.class, settingsParam.getVarray());
ArgValidator.checkEntity(neighborhood, settingsParam.getVarray(), false);
} else {
throw APIException.badRequests.protectionVirtualPoolArrayMissing();
}
// Validate the protection copy virtual pool if it has been specified.
if (settingsParam.getVpool() != null && !String.valueOf(settingsParam.getVpool()).isEmpty()) {
ArgValidator.checkUri(settingsParam.getVpool());
VirtualPool protectionCopyVPool = dbClient.queryObject(VirtualPool.class, settingsParam.getVpool());
ArgValidator.checkEntity(protectionCopyVPool, settingsParam.getVpool(), false);
}
// Validate the copy policy
if (settingsParam.getCopyPolicy() != null && settingsParam.getCopyPolicy().getJournalSize() != null) {
// Make sure the journal size is of the correct format
if (!isParsableToDouble(settingsParam.getCopyPolicy().getJournalSize())
&& !settingsParam.getCopyPolicy().getJournalSize().matches(JOURNAL_REGEX_1)
&& !settingsParam.getCopyPolicy().getJournalSize().matches(JOURNAL_REGEX_2)
&& !settingsParam.getCopyPolicy().getJournalSize().equals(JOURNAL_MIN)) {
throw APIException.badRequests.protectionVirtualPoolJournalSizeInvalid("copy", settingsParam.getCopyPolicy()
.getJournalSize());
}
}
}
}
}
/**
* quick method to test for a string, in a method to shield the exception from the caller.
*
* @param i string
* @return true if it is a number (double, in this case)
*/
private boolean isParsableToDouble(String i)
{
return i.matches("\\d+\\.\\d+");
}
/**
* Checks to see if the virtual pool has expandable expansion enabled
* and mirroring enabled. This combination is not allowed.
*
* @param createParam The create parameter
*/
private void validateNoExpansionWithMirroringOrFail(BlockVirtualPoolParam createParam) {
Map<String, Object> protections = getProtectionParameters(createParam);
if (protections.get(PROTECTION_MIRRORS) != null &&
(createParam.getExpandable() == null || createParam.getExpandable())) {
throw APIException.badRequests.virtualPoolDoesNotSupportExpandable();
}
}
/**
* Checks to see if the current virtual pool has expandable enabled
* and the update request has mirroring enabled, or vice-versa. This combination
* is not allowed.
*
* @param virtualPool The Virtual Pool being updated
* @param updateParam The parameter containing the updates
*/
private void validateNoExpansionWithMirroringOrFail(VirtualPool virtualPool,
BlockVirtualPoolUpdateParam updateParam) {
if (updateParam.getProtection() != null) {
if ((updateParam.getProtection().enablesContinuousCopies() && updateParam.allowsExpansion())
|| (updateParam.getProtection().enablesContinuousCopies()
&& VirtualPool.vPoolAllowsExpansion(virtualPool)
&& (updateParam.getExpandable() == null || updateParam.allowsExpansion()))) {
throwExpandableWithMirroringException(virtualPool);
}
}
}
/**
* Convenience method for throwing an exception when an attempt is
* made to set the expandable attribute to true when mirroring is
* enabled.
*
* @param virtualPool
*/
private void throwExpandableWithMirroringException(VirtualPool virtualPool) {
throw APIException.badRequests.protectionVirtualPoolDoesNotSupportExpandingMirrors(virtualPool.getId());
}
/**
* Convenience method for throwing an exception when an attempt is
* made to set a source policy when no protection is set.
*/
private void throwServicePolicyNoProtectionException() {
throw APIException.badRequests.protectionNotSpecifiedInVirtualPool();
}
private Predicate<Map.Entry<String, Object>> paramEntryValueNotNull() {
return new Predicate<Map.Entry<String, Object>>() {
@Override
public boolean apply(Map.Entry<String, Object> paramEntry) {
return paramEntry.getValue() != null;
}
};
}
private Predicate<Map.Entry<String, Object>> paramEntryValueNotNone() {
return new Predicate<Map.Entry<String, Object>>() {
@Override
public boolean apply(Map.Entry<String, Object> paramEntry) {
return !paramEntry.getValue().toString().toLowerCase().equals("none");
}
};
}
/**
* Validates that the multi-volume consistency flag is set if RP
* protection is being specified.
*
* @param createParam the Virtual Pool create param
*/
private void validateMultiVolumeConsistency(BlockVirtualPoolParam createParam) {
if (createParam.getProtection() != null
&& createParam.getProtection().specifiesRPProtection()
&& (createParam.getMultiVolumeConsistency() == null || !createParam.getMultiVolumeConsistency())) {
// RP protection is specified so the volume consistency flag must be set
throw APIException.badRequests.multiVolumeConsistencyMustBeEnabledWithRP();
}
}
/**
*
*
* @param createParam
* @param dbClient
*/
private void validateRpNonHaCopyVpools(BlockVirtualPoolParam createParam, DbClient dbClient) {
if (createParam.getProtection() != null
&& createParam.getProtection().specifiesRPProtection()
&& !createParam.specifiesHighAvailability()) {
// validate the copy target vpools
BlockVirtualPoolProtectionParam protection = createParam.getProtection();
VirtualPoolProtectionRPParam rpParam = protection.getRecoverPoint();
for (VirtualPoolProtectionVirtualArraySettingsParam protectionSettings : rpParam.getCopies()) {
if (protectionSettings.getVpool() != null) {
VirtualPool protectionVirtualPool =
dbClient.queryObject(VirtualPool.class, protectionSettings.getVpool());
if (VirtualPool.vPoolSpecifiesHighAvailability(protectionVirtualPool)) {
// throw exception - all target Vpools must not support HA if the base vpool
// does not specify HA.
throw APIException.badRequests.nonVirtualToVirtualProtectionNotSupported();
}
}
}
}
}
private void validateRpNonHaCopyVpools(BlockVirtualPoolUpdateParam updateParam, DbClient dbClient) {
if (updateParam.getProtection() != null
&& updateParam.getProtection().specifiesRPProtection()
&& !updateParam.specifiesHighAvailability()) {
// validate the copy target vpools
BlockVirtualPoolProtectionUpdateParam protection = updateParam.getProtection();
VirtualPoolProtectionRPChanges rpParam = protection.getRecoverPoint();
if (rpParam != null) {
for (VirtualPoolProtectionVirtualArraySettingsParam protectionSettings : rpParam.getAdd()) {
if (protectionSettings.getVpool() != null) {
VirtualPool protectionVirtualPool =
dbClient.queryObject(VirtualPool.class, protectionSettings.getVpool());
if (VirtualPool.vPoolSpecifiesHighAvailability(protectionVirtualPool)) {
// throw exception - all target Vpools must not support HA if the base vpool
// does not specify HA.
throw APIException.badRequests.nonVirtualToVirtualProtectionNotSupported();
}
}
}
}
}
}
/**
* Validates that the multi-volume consistency flag is set if RP
* protection is being specified.
*
* @param virtualPool the Virtual Pool to validate
*/
private void validateMultiVolumeConsistency(VirtualPool virtualPool) {
if (VirtualPool.vPoolSpecifiesProtection(virtualPool)
&& (virtualPool.getMultivolumeConsistency() == null || !virtualPool.getMultivolumeConsistency())) {
// RP protection is specified so the volume consistency flag must be set
throw APIException.badRequests.multiVolumeConsistencyMustBeEnabledWithRP();
}
}
/**
* Validates the MetroPoint VirtualPool configuration if MetroPoint has been selected.
*
* @param createParam
* @param dbClient
*/
private void validateMetroPoint(BlockVirtualPoolParam createParam, DbClient dbClient) {
if (createParam != null && createParam.getHighAvailability() != null) {
if (createParam.getHighAvailability().getMetroPoint() != null && createParam.getHighAvailability().getMetroPoint()) {
// If MetroPoint is selected, verify that RP protection and VPlex distributed are selected.
if (createParam.getHighAvailability().getType() == null
|| createParam.getHighAvailability().getType().equals(HIGH_AVAILABILITY_NONE)
|| !createParam.getHighAvailability().getType().equals(VirtualPool.HighAvailabilityType.vplex_distributed.name())
|| createParam.getProtection() == null
|| !createParam.getProtection().specifiesRPProtection()) {
throw APIException.badRequests.metroPointConfigurationNotSupported();
}
}
}
}
/**
* Validates the MetroPoint VirtualPool configuration if MetroPoint has been selected.
*
* @param createParam
*/
private void validateMetroPoint(VirtualPool virtualPool) {
// If MetroPoint is selected, verify that RP protection and VPlex distributed are selected.
if (virtualPool.getMetroPoint() != null && virtualPool.getMetroPoint()) {
String highAvailability = virtualPool.getHighAvailability();
if (highAvailability == null
|| !VirtualPool.HighAvailabilityType.vplex_distributed.name().equals(highAvailability)
|| !VirtualPool.vPoolSpecifiesProtection(virtualPool)) {
throw APIException.badRequests.metroPointConfigurationNotSupported();
}
}
}
}