/*************************************************************************
* Copyright 2009-2014 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.cloudwatch.service;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmEntity;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmHistory;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmManager;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricEntity;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricStatistics;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricUtils;
import com.eucalyptus.cloudwatch.common.msgs.AlarmNames;
import com.eucalyptus.cloudwatch.common.msgs.Datapoint;
import com.eucalyptus.cloudwatch.common.msgs.Dimension;
import com.eucalyptus.cloudwatch.common.msgs.DimensionFilter;
import com.eucalyptus.cloudwatch.common.msgs.DimensionFilters;
import com.eucalyptus.cloudwatch.common.msgs.Dimensions;
import com.eucalyptus.cloudwatch.common.msgs.MetricData;
import com.eucalyptus.cloudwatch.common.msgs.MetricDatum;
import com.eucalyptus.cloudwatch.common.msgs.ResourceList;
import com.eucalyptus.cloudwatch.common.msgs.StatisticSet;
import com.eucalyptus.cloudwatch.common.msgs.Statistics;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricEntity.MetricType;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.Units;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.sf.json.JSONException;
import net.sf.json.JSONSerializer;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CloudWatchServiceFieldValidator {
private static final String SystemMetricPrefix = "AWS/";
static void validatePeriodAndEvaluationPeriodsNotAcrossDays(Integer period,
Integer evaluationPeriods) throws CloudWatchException{
if (period * evaluationPeriods > 86400) {
throw new InvalidParameterCombinationException("Metrics cannot be checked across more than a day (EvaluationPeriods * Period must be <= 86400).");
}
}
static void validateNotTooManyDataPoints(Date startTime, Date endTime,
Integer period, long maxDataPoints) throws CloudWatchException {
NumberFormat nf = NumberFormat.getInstance();
long possibleRequestedDataPoints = (endTime.getTime() - startTime.getTime()) / (1000L * period);
if (possibleRequestedDataPoints > maxDataPoints) {
throw new InvalidParameterCombinationException("You have requested up to " + nf.format(possibleRequestedDataPoints)+ " datapoints, which exceeds the limit of " + nf.format(maxDataPoints) + ". You may reduce the datapoints requested by increasing Period, or decreasing the time range.");
}
}
static int NUM_SYSTEM_METRIC_DATA_POINTS = 50;
static int NUM_CUSTOM_METRIC_DATA_POINTS = 20;
static List<MetricDatum> validateMetricData(MetricData metricData, MetricType metricType) throws CloudWatchException {
List<MetricDatum> metricDataCollection = null;
if (metricData != null) {
metricDataCollection = metricData.getMember();
;
}
if (metricDataCollection == null) {
throw new MissingParameterException(
"The parameter MetricData is required.");
}
if (metricDataCollection.size() < 1) {
throw new MissingParameterException(
"The parameter MetricData is required.");
}
if (metricDataCollection.size() > NUM_CUSTOM_METRIC_DATA_POINTS && metricType == MetricType.Custom) {
throw new InvalidParameterValueException(
"The collection MetricData must not have a size greater than " + NUM_CUSTOM_METRIC_DATA_POINTS);
}
if (metricDataCollection.size() > NUM_SYSTEM_METRIC_DATA_POINTS && metricType == MetricType.Custom) {
throw new InvalidParameterValueException(
"The collection MetricData must not have a size greater than " + NUM_SYSTEM_METRIC_DATA_POINTS + " (for system metrics)");
}
int ctr = 1;
for (MetricDatum metricDatum : metricDataCollection) {
validateMetricDatum(metricDatum, "MetricData.member." + ctr);
ctr++;
}
return metricDataCollection;
}
static Date validateStartDate(Date startDate, boolean required)
throws CloudWatchException {
return validateTimestamp(startDate, "StartDate", required);
}
static Date validateEndDate(Date endDate, boolean required)
throws CloudWatchException {
return validateTimestamp(endDate, "EndDate", required);
}
static Date validateStartTime(Date startTime, boolean required)
throws CloudWatchException {
return validateTimestamp(startTime, "StartTime", required);
}
static Date validateEndTime(Date endTime, boolean required)
throws CloudWatchException {
return validateTimestamp(endTime, "EndTime", required);
}
static MetricDatum validateMetricDatum(MetricDatum metricDatum, String name) throws CloudWatchException {
if (metricDatum == null) {
throw new MissingParameterException("The parameter " + name + " is required.");
}
validateDimensions(metricDatum.getDimensions(), name + ".Dimensions");
validateDimensionsNoDuplicateKeys(metricDatum.getDimensions());
validateMetricName(metricDatum.getMetricName(), name + ".MetricName",
true);
validateWithinTwoWeeks(metricDatum.getTimestamp(), name + "Timestamp");
validateUnits(metricDatum.getUnit(), name + ".Unit", true);
validateValueAndStatisticSet(metricDatum.getValue(), name + ".Value",
metricDatum.getStatisticValues(), name + ".StatisticValues");
return metricDatum;
}
static void validateDimensionsNoDuplicateKeys(Dimensions dimensions) throws InvalidParameterValueException {
Set<String> seenDimensionNames = Sets.newHashSet();
if (dimensions != null && dimensions.getMember() != null) {
for (Dimension dimension: dimensions.getMember()) {
if (seenDimensionNames.contains(dimension.getName())) {
throw new InvalidParameterValueException("No Metric may specify the same dimension twice.");
} else {
seenDimensionNames.add(dimension.getName());
}
}
}
}
static void validateWithinTwoWeeks(Date timestamp, String name) throws CloudWatchException {
if (timestamp == null) return;
Date now = new Date();
Date twoWeeksAgo = new Date(now.getTime() - 2 * 7 * 24 * 3600 * 1000L);
long BUFFER = 2 * 3600 * 1000L; // two hours
if (timestamp.getTime() > now.getTime() + BUFFER || timestamp.getTime() < twoWeeksAgo.getTime() - BUFFER) {
throw new InvalidParameterValueException("The parameter " + name + ".Timestamp must specify a time within the past two weeks.");
}
}
static void validateValueAndStatisticSet(Double value, String valueName, StatisticSet statisticValues, String statisticValuesName) throws CloudWatchException {
if (value == null && statisticSetHasNoFields(statisticValues)) {
throw new MissingParameterException("At least one of the parameters " + valueName + " or "
+ statisticValuesName + " must be specified.");
}
if (value != null && !statisticSetHasNoFields(statisticValues)) {
throw new InvalidParameterCombinationException("The parameters " + valueName + " and "
+ statisticValuesName + " are mutually exclusive and you have specified both.");
}
if (value != null) return; // value is set
validateAllStatisticSetFields(statisticValues, statisticValuesName);
if (statisticValues.getMaximum() < statisticValues.getMinimum()) {
throw new MissingParameterException("The parameter " + statisticValuesName+ ".Maximum must be greater than " + statisticValuesName + ".Minimum.");
}
if (statisticValues.getSampleCount() < 0) {
throw new MissingParameterException("The parameter " + statisticValuesName+ ".SampleCount must be greater than 0.");
}
if (statisticValues.getSampleCount() == 0.0) {
throw new MissingParameterException("The parameter " + statisticValuesName+ ".SampleCount must not equal 0.");
}
}
static void validateAllStatisticSetFields(StatisticSet statisticValues, String statisticValuesName) throws CloudWatchException {
StringBuilder errors = new StringBuilder();
boolean haveErrors = false;
if (statisticValues == null || statisticValues.getMaximum() == null) {
if (haveErrors) {
errors.append("\n");
}
errors.append("The parameter " + statisticValuesName + ".Maximum is required.");
haveErrors = true;
}
if (statisticValues == null || statisticValues.getMinimum() == null) {
if (haveErrors) {
errors.append("\n");
}
errors.append("The parameter " + statisticValuesName + ".Minimum is required.");
haveErrors = true;
}
if (statisticValues == null || statisticValues.getSampleCount() == null) {
if (haveErrors) {
errors.append("\n");
}
errors.append("The parameter " + statisticValuesName + ".SampleCount is required.");
haveErrors = true;
}
if (statisticValues == null || statisticValues.getSum() == null) {
if (haveErrors) {
errors.append("\n");
}
errors.append("The parameter " + statisticValuesName + ".Sum is required.");
haveErrors = true;
}
if (haveErrors) {
throw new MissingParameterException(errors.toString());
}
}
static boolean statisticSetHasNoFields(StatisticSet statisticValues) {
return (statisticValues == null) || (
(statisticValues.getMaximum()) == null && (statisticValues.getMinimum() == null) &&
(statisticValues.getSampleCount()) == null && (statisticValues.getSum() == null));
}
static Units validateUnits(String unit, boolean useNoneIfNull) throws CloudWatchException {
return validateUnits(unit, "Unit", useNoneIfNull);
}
static Date validateTimestamp(Date timestamp, String name, boolean required)
throws CloudWatchException {
if (timestamp == null) {
if (required) {
throw new MissingParameterException("The parameter " + name + " is required.");
}
}
return timestamp;
}
static Integer validateEvaluationPeriods(Integer evaluationPeriods,
boolean required) throws CloudWatchException {
if (evaluationPeriods == null) {
if (required) {
throw new MissingParameterException(
"The parameter EvaluationPeriods is required.");
}
}
if (evaluationPeriods < 1) {
throw new InvalidParameterValueException(
"The parameter EvaluationPeriods must be greater than or equal to 1.");
}
return evaluationPeriods;
}
static String validateMetricName(String metricName, boolean required)
throws CloudWatchException {
return validateMetricName(metricName, "MetricName", required);
}
static Dimensions validateDimensions(Dimensions dimensions)
throws CloudWatchException {
return validateDimensions(dimensions, "Dimensions");
}
static DimensionFilters validateDimensionFilters(
DimensionFilters dimensionFilters)
throws CloudWatchException {
return validateDimensionFilters(dimensionFilters, "Dimensions");
}
static Double validateDouble(Double value, String name, boolean required)
throws CloudWatchException {
if (value == null) {
if (required) {
throw new MissingParameterException("The parameter " + name + " is required.");
}
}
return value;
}
static Double validateThreshold(Double threshold, boolean required)
throws CloudWatchException {
return validateDouble(threshold, "Threshold", required);
}
static AlarmEntity.ComparisonOperator validateComparisonOperator(
String comparisonOperator, boolean required) throws CloudWatchException {
return validateEnum(comparisonOperator, "ComparisonOperator",
AlarmEntity.ComparisonOperator.class, required);
}
static String validateAlarmDescription(String alarmDescription) throws CloudWatchException {
return validateStringLength(alarmDescription, "AlarmDescription", 0, 255, false);
}
static Boolean validateActionsEnabled(Boolean actionsEnabled, boolean useTrueIfNull) throws CloudWatchException {
if (actionsEnabled == null) {
if (useTrueIfNull) {
return Boolean.TRUE;
}
}
return actionsEnabled;
}
static Collection<String> validateActions(ResourceList actions,
Map<String, String> dimensionMap, String name)
throws CloudWatchException {
Collection<String> actionsCollection = null;
if (actions != null) {
actionsCollection = actions.getMember();
;
}
if (actionsCollection == null) {
return actionsCollection;
}
if (actionsCollection.size() > 5) {
throw new InvalidParameterValueException("The collection " + name
+ " must not have a size greater than 5.");
}
int ctr = 1;
for (String action : actionsCollection) {
validateAction(action, dimensionMap, name + ".member." + ctr);
ctr++;
}
return actionsCollection;
}
static void validateAction(String action, Map<String, String> dimensionMap,
String name) throws CloudWatchException {
if (AlarmManager.ActionManager.getAction(action, dimensionMap) == null) {
throw new InvalidParameterValueException("The parameter " + name + "'" + action
+ "' is an unsupported action for this metric and dimension list.");
}
}
static String validateAlarmNamePrefix(String alarmNamePrefix,
boolean required) throws CloudWatchException {
return validateStringLength(alarmNamePrefix, "AlarmNamePrefix", 1, 255,
required);
}
static String validateActionPrefix(String actionPrefix, boolean required)
throws CloudWatchException {
return validateStringLength(actionPrefix, "ActionPrefix", 1, 1024, required);
}
static Statistics validateStatistics(Statistics statistics)
throws CloudWatchException {
Collection<String> statisticCollection = null;
if (statistics != null) {
statisticCollection = statistics.getMember();
}
if (statisticCollection == null) {
throw new MissingParameterException("The parameter Statistics is required.");
}
if (statisticCollection.size() < 1) {
throw new MissingParameterException("The parameter Statistics is required.");
}
if (statisticCollection.size() > 5) {
throw new InvalidParameterValueException(
"The collection MetricData must not have a size greater than 5.");
}
int ctr = 1;
String[] statisticValues = new String[] { "Average", "Sum", "SampleCount",
"Maximum", "Minimum" };
for (String statistic : statisticCollection) {
if (statistic == null) {
throw new InvalidParameterValueException("The parameter Statistics.member." + ctr
+ " is required.");
}
if (!Arrays.asList(statisticValues).contains(statistic)) {
throw new InvalidParameterValueException("The parameter Statistics.member." + ctr
+ " must be a value in the set " + Arrays.asList(statisticValues) + ".");
}
ctr++;
}
return statistics;
}
static DimensionFilters validateDimensionFilters(
DimensionFilters dimensionFilters, String name)
throws CloudWatchException {
Collection<DimensionFilter> dimensionFiltersCollection = null;
if (dimensionFilters != null) {
dimensionFiltersCollection = dimensionFilters.getMember();
}
if (dimensionFiltersCollection == null) {
return dimensionFilters;
}
if (dimensionFiltersCollection.size() > 10) {
throw new InvalidParameterValueException("The collection " + name
+ " must not have a size greater than 10.");
}
int ctr = 1;
for (DimensionFilter dimensionFilter : dimensionFiltersCollection) {
validateStringLength(dimensionFilter.getName(), name + ".member." + (ctr)
+ ".Name", 1, 255, true);
validateStringLength(dimensionFilter.getValue(), name + ".member."
+ (ctr) + ".Value", 1, 255, true);
ctr++;
}
return dimensionFilters;
}
static Dimensions validateDimensions(Dimensions dimensions, String name) throws CloudWatchException {
Collection<Dimension> dimensionsCollection = null;
if (dimensions != null) {
dimensionsCollection = dimensions.getMember();
}
if (dimensions == null) {
return dimensions;
}
if (dimensionsCollection.size() > 10) {
throw new InvalidParameterValueException("The collection " + name
+ " must not have a size greater than 10.");
}
int ctr = 1;
for (Dimension dimension : dimensionsCollection) {
validateStringLength(dimension.getName(), name + ".member." + (ctr)
+ ".Name", 1, 255, true);
validateStringLength(dimension.getValue(), name + ".member." + (ctr)
+ ".Value", 1, 255, true);
ctr++;
}
return dimensions;
}
static Units validateUnits(String unit, String name, boolean useNoneIfNull) throws CloudWatchException {
if (unit == null) {
if (useNoneIfNull) {
return Units.None;
} else {
return null;
}
}
try {
return Units.fromValue(unit);
} catch (IllegalArgumentException ex) {
throw new InvalidParameterValueException("The parameter " + name + " must be a value in the set "
+ Arrays.asList(Units.values()) +".");
}
}
static AlarmEntity.Statistic validateStatistic(String statistic, boolean required)
throws CloudWatchException {
return validateEnum(statistic, "Statistic", AlarmEntity.Statistic.class, required);
}
static Integer validatePeriod(Integer period, boolean required)
throws CloudWatchException {
if (period == null) {
if (required) {
throw new MissingParameterException("The parameter Period is required.");
} else {
return period;
}
}
if (period < 0) {
throw new InvalidParameterValueException("The parameter Period must be greater than 0.");
}
if (period == 0) {
throw new InvalidParameterValueException("The parameter Period must not equal 0.");
}
if (period % 60 != 0) {
throw new InvalidParameterValueException(
"The parameter Period must be a multiple of 60.");
}
return period;
}
static String validateNamespace(String namespace, boolean required)
throws CloudWatchException {
namespace = validateStringLength(namespace, "Namespace", 1, 255, required);
return namespace;
}
static String validateMetricName(String metricName, String name,
boolean required) throws CloudWatchException {
return validateStringLength(metricName, name, 1, 255, required);
}
static void validateDateOrder(Date startDate, Date endDate,
String startDateName, String endDateName, boolean startDateRequired,
boolean endDateRequired) throws CloudWatchException {
if (startDate != null && endDate != null) {
if (startDate.after(endDate)) {
throw new InvalidParameterValueException("The parameter " + endDateName
+ " must be greater than " + startDateName + ".");
}
if (startDate.equals(endDate)) {
throw new InvalidParameterValueException("The parameter " + startDateName
+ " must not equal parameter " + endDateName + ".");
}
}
if (startDate == null && startDateRequired) {
throw new MissingParameterException("The parameter " + startDateName
+ " is required.");
}
if (endDate == null && endDateRequired) {
throw new MissingParameterException("The parameter " + endDateName
+ " is required.");
}
}
static AlarmHistory.HistoryItemType validateHistoryItemType(String historyItemType,
boolean required) throws CloudWatchException {
return validateEnum(historyItemType, "HistoryItemType",
AlarmHistory.HistoryItemType.class, required);
}
static Collection<String> validateAlarmNames(AlarmNames alarmNames,
boolean required) throws CloudWatchException {
Collection<String> alarmNamesCollection = null;
if (alarmNames != null) {
alarmNamesCollection = alarmNames.getMember();
}
if (alarmNamesCollection == null) {
if (required) {
throw new MissingParameterException(
"The parameter AlarmNames is required.");
}
return alarmNamesCollection;
}
if (alarmNamesCollection.size() < 1 && required) {
throw new MissingParameterException(
"The parameter AlarmNames is required.");
}
if (alarmNamesCollection.size() > 100) {
throw new InvalidParameterValueException(
"The collection AlarmNames must not have a size greater than 100.");
}
int ctr = 1;
for (String alarmName : alarmNamesCollection) {
validateAlarmName(alarmName, "AlarmName.member." + (ctr++), true);
}
return alarmNamesCollection;
}
static <T extends Enum<T>> T validateEnum(String value, String name,
Class<T> enumType, boolean required) throws CloudWatchException {
try {
return Enum.valueOf(enumType, value);
} catch (IllegalArgumentException ex) {
throw new InvalidParameterValueException("The parameter " + name + " must be a value in the set "
+ Arrays.asList(enumType.getEnumConstants()) + ".");
} catch (NullPointerException ex) {
if (required) {
throw new MissingParameterException("The parameter " + name + " is required.");
}
return null;
}
}
static AlarmEntity.StateValue validateStateValue(String stateValue, boolean required)
throws CloudWatchException {
return validateEnum(stateValue, "StateValue", AlarmEntity.StateValue.class, required);
}
static String validateStateReasonData(String stateReasonData,
boolean required) throws CloudWatchException {
stateReasonData = validateStringLength(stateReasonData, "StateReasonData",
0, 4000, required);
stateReasonData = validateJSON(stateReasonData, "StateReasonData", required);
return stateReasonData;
}
static String validateJSON(String value, String name, boolean required)
throws CloudWatchException {
if (value == null) {
if (required) {
throw new MissingParameterException("The parameter " + name + " is required.");
} else {
return value;
}
}
try {
JSONSerializer.toJSON(value);
} catch (JSONException ex) {
throw new InvalidFormatException(name
+ " was not syntactically valid JSON");
}
return value;
}
static String validateStringLength(String value, String name, int minLength,
int maxLength, boolean required) throws CloudWatchException {
if (value == null || value.isEmpty() ) {
if (required) {
throw new MissingParameterException("The parameter " + name + " is required.");
} else {
return value;
}
}
if (value.length() < minLength) {
throw new InvalidParameterValueException("The parameter " + name + " must be longer than " + (minLength - 1) + " character" + ((minLength == 2) ? "" : "s" ) + ".");
}
if (value.length() > maxLength) {
throw new InvalidParameterValueException("The parameter " + name + " must be shorter than " + (maxLength + 1) + " character" + ((maxLength == 0) ? "" : "s" ) + ".");
}
return value;
}
static String validateAlarmName(String alarmName, boolean required)
throws CloudWatchException {
return validateAlarmName(alarmName, "AlarmName", required);
}
static String validateAlarmName(String alarmNameValue, String alarmNameKey,
boolean required) throws CloudWatchException {
return validateStringLength(alarmNameValue, alarmNameKey, 1, 255, required);
}
static String validateStateReason(String stateReason, boolean required)
throws CloudWatchException {
return validateStringLength(stateReason, "StateReason", 0, 1024, required);
}
static MetricEntity.MetricType getMetricTypeFromNamespace(String namespace) {
return namespace.startsWith(SystemMetricPrefix) ? MetricEntity.MetricType.System
: MetricEntity.MetricType.Custom;
}
static ArrayList<Datapoint> convertMetricStatisticsToDatapoints(
Statistics statistics, Collection<MetricStatistics> metrics) {
ArrayList<Datapoint> datapoints = Lists.newArrayList();
boolean wantsAverage = statistics.getMember().contains("Average");
boolean wantsSum = statistics.getMember().contains("Sum");
boolean wantsSampleCount = statistics.getMember().contains("SampleCount");
boolean wantsMaximum = statistics.getMember().contains("Maximum");
boolean wantsMinimum = statistics.getMember().contains("Minimum");
for (MetricStatistics metricStatistics : metrics) {
Datapoint datapoint = new Datapoint();
datapoint.setTimestamp(metricStatistics.getTimestamp());
datapoint.setUnit(metricStatistics.getUnits().toString());
if (wantsSum) {
datapoint.setSum(metricStatistics.getSampleSum());
}
if (wantsSampleCount) {
datapoint.setSampleCount(metricStatistics.getSampleSize());
}
if (wantsMaximum) {
datapoint.setMaximum(metricStatistics.getSampleMax());
}
if (wantsMinimum) {
datapoint.setMinimum(metricStatistics.getSampleMin());
}
if (wantsAverage) {
datapoint.setAverage(MetricUtils.average(
metricStatistics.getSampleSum(), metricStatistics.getSampleSize()));
}
datapoints.add(datapoint);
}
return datapoints;
}
static void validateNotBothAlarmNamesAndAlarmNamePrefix(
Collection<String> alarmNames, String alarmNamePrefix)
throws CloudWatchException {
if (alarmNames != null && alarmNamePrefix != null) {
throw new InvalidParameterCombinationException(
"AlarmNamePrefix and AlarmNames.member are mutually exclusive");
}
}
static Integer validateMaxRecords(Integer maxRecords)
throws CloudWatchException {
if (maxRecords == null) {
return 50; // this is from experimentation
}
if (maxRecords < 1) {
throw new InvalidParameterValueException("The parameter MaxRecords must be greater than or equal to 1.");
}
if (maxRecords > 100) {
throw new InvalidParameterValueException("The parameter MaxRecords must be less than or equal to 100.");
}
return maxRecords;
}
}