From e58d91e78dafce2b68b5cd852b233c4a6dcad0c2 Mon Sep 17 00:00:00 2001 From: Jeremy Whitlock Date: Fri, 27 Apr 2012 18:43:08 -0600 Subject: [PATCH] Extended support to apis/cloudwatch for getting metrics. * Updated to allow supplying any dimension to describe the metric * Updated to allow supplying more than one statistic to retrieve --- .../cloudwatch/CloudWatchAsyncClient.java | 13 + .../jclouds/cloudwatch/CloudWatchClient.java | 15 +- .../domain/GetMetricStatisticsResponse.java | 111 ++++++ .../domain/ListMetricsResponse.java | 2 +- .../options/GetMetricStatisticsOptions.java | 7 +- .../options/GetMetricStatisticsOptionsV2.java | 371 ++++++++++++++++++ .../options/ListMetricsOptions.java | 78 ++-- .../GetMetricStatisticsResponseHandlerV2.java | 90 +++++ .../cloudwatch/CloudWatchAsyncClientTest.java | 55 +++ .../cloudwatch/CloudWatchClientLiveTest.java | 53 +++ 10 files changed, 752 insertions(+), 43 deletions(-) create mode 100644 apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/GetMetricStatisticsResponse.java create mode 100644 apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptionsV2.java create mode 100644 apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/xml/GetMetricStatisticsResponseHandlerV2.java diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchAsyncClient.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchAsyncClient.java index e2be54e122..83c93c7ce4 100644 --- a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchAsyncClient.java +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchAsyncClient.java @@ -21,12 +21,15 @@ package org.jclouds.cloudwatch; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.aws.filters.FormSigner; import org.jclouds.cloudwatch.domain.Datapoint; +import org.jclouds.cloudwatch.domain.GetMetricStatisticsResponse; import org.jclouds.cloudwatch.domain.ListMetricsResponse; import org.jclouds.cloudwatch.domain.Statistics; import org.jclouds.cloudwatch.functions.ISO8601Format; import org.jclouds.cloudwatch.options.GetMetricStatisticsOptions; +import org.jclouds.cloudwatch.options.GetMetricStatisticsOptionsV2; import org.jclouds.cloudwatch.options.ListMetricsOptions; import org.jclouds.cloudwatch.xml.GetMetricStatisticsResponseHandler; +import org.jclouds.cloudwatch.xml.GetMetricStatisticsResponseHandlerV2; import org.jclouds.cloudwatch.xml.ListMetricsResponseHandler; import org.jclouds.javax.annotation.Nullable; import org.jclouds.location.functions.RegionToEndpointOrProviderIfNull; @@ -60,6 +63,7 @@ public interface CloudWatchAsyncClient { /** * @see CloudWatchClient#getMetricStatisticsInRegion */ + @Deprecated @POST @Path("/") @XMLResponseParser(GetMetricStatisticsResponseHandler.class) @@ -83,4 +87,13 @@ public interface CloudWatchAsyncClient { @FormParams(keys = "Action", values = "ListMetrics") ListenableFuture listMetrics(ListMetricsOptions options); + /** + * @see CloudWatchClient#getMetricStatistics(org.jclouds.cloudwatch.options.GetMetricStatisticsOptionsV2) + */ + @POST + @Path("/") + @XMLResponseParser(GetMetricStatisticsResponseHandlerV2.class) + @FormParams(keys = "Action", values = "GetMetricStatistics") + ListenableFuture getMetricStatistics(GetMetricStatisticsOptionsV2 options); + } diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchClient.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchClient.java index 7a643510ca..b73b34c907 100644 --- a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchClient.java +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/CloudWatchClient.java @@ -19,9 +19,11 @@ package org.jclouds.cloudwatch; import org.jclouds.cloudwatch.domain.Datapoint; +import org.jclouds.cloudwatch.domain.GetMetricStatisticsResponse; import org.jclouds.cloudwatch.domain.ListMetricsResponse; import org.jclouds.cloudwatch.domain.Statistics; import org.jclouds.cloudwatch.options.GetMetricStatisticsOptions; +import org.jclouds.cloudwatch.options.GetMetricStatisticsOptionsV2; import org.jclouds.cloudwatch.options.ListMetricsOptions; import org.jclouds.concurrent.Timeout; import org.jclouds.javax.annotation.Nullable; @@ -79,10 +81,10 @@ public interface CloudWatchClient { * @param options * more filtering options (e.g. instance ID) */ + @Deprecated Set getMetricStatisticsInRegion(@Nullable String region, String metricName, String namespace, Date startTime, Date endTime, int period, Statistics statistics, GetMetricStatisticsOptions... options); - /** * Returns a list of valid metrics stored for the AWS account owner. * @@ -94,8 +96,17 @@ public interface CloudWatchClient { * * @param options the options describing the metrics query * - * @return the list of valid metrics based on the AWS account owner and the options passed in + * @return the response object */ ListMetricsResponse listMetrics(ListMetricsOptions options); + /** + * Gets statistics for the specified metric. + * + * @param options the options describing the metric statistics query + * + * @return the response object + */ + GetMetricStatisticsResponse getMetricStatistics(GetMetricStatisticsOptionsV2 options); + } diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/GetMetricStatisticsResponse.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/GetMetricStatisticsResponse.java new file mode 100644 index 0000000000..3503f65489 --- /dev/null +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/GetMetricStatisticsResponse.java @@ -0,0 +1,111 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.cloudwatch.domain; + +import com.google.common.collect.Sets; +import org.jclouds.javax.annotation.Nullable; + +import java.util.Iterator; +import java.util.Set; + +/** + * @see + * + * @author Jeremy Whitlock + */ +public class GetMetricStatisticsResponse { + + private final Set datapoints; + private final String label; + + public GetMetricStatisticsResponse(@Nullable Set datapoints, String label) { + if (datapoints == null) { + this.datapoints = Sets.newLinkedHashSet(); + } else { + this.datapoints = datapoints; + } + this.label = label; + } + + /** + * return the list of {@link Datapoint} for the metric + */ + public Set getDatapoints() { + return datapoints; + } + + /** + * return the label describing the specified metric + */ + public String getLabel() { + return label; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GetMetricStatisticsResponse other = (GetMetricStatisticsResponse)obj; + if (datapoints == null) { + if (other.datapoints != null) + return false; + } else if (!datapoints.equals(other.datapoints)) + return false; + if (label == null) { + if (other.label != null) + return false; + } else if (!label.equals(other.label)) + return false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + builder.append("[label=") + .append(label) + .append(", datapoints=["); + + Iterator iterator = datapoints.iterator(); + + while (iterator.hasNext()) { + builder.append(iterator.next()); + + if (iterator.hasNext()) { + builder.append(", "); + } + } + + builder.append("]]"); + + return builder.toString(); + } + +} diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/ListMetricsResponse.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/ListMetricsResponse.java index ed74019d34..a22781b434 100644 --- a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/ListMetricsResponse.java +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/domain/ListMetricsResponse.java @@ -34,7 +34,7 @@ public class ListMetricsResponse { private final Set metrics; private final String nextToken; - public ListMetricsResponse(Set metrics, String nextToken) { + public ListMetricsResponse(@Nullable Set metrics, @Nullable String nextToken) { // Default to an empty set if (metrics == null) { this.metrics = Sets.newLinkedHashSet(); diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptions.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptions.java index 62eb050733..7a3c5de87a 100644 --- a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptions.java +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptions.java @@ -18,12 +18,12 @@ */ package org.jclouds.cloudwatch.options; -import static com.google.common.base.Preconditions.checkNotNull; - import org.jclouds.aws.util.AWSUtils; import org.jclouds.cloudwatch.domain.Unit; import org.jclouds.http.options.BaseHttpRequestOptions; +import static com.google.common.base.Preconditions.checkNotNull; + /** * Options used to control metric statistics are returned * @@ -32,6 +32,7 @@ import org.jclouds.http.options.BaseHttpRequestOptions; * /> * @author Andrei Savu */ +@Deprecated public class GetMetricStatisticsOptions extends BaseHttpRequestOptions { public static final GetMetricStatisticsOptions NONE = new GetMetricStatisticsOptions(); @@ -73,5 +74,7 @@ public class GetMetricStatisticsOptions extends BaseHttpRequestOptions { GetMetricStatisticsOptions options = new GetMetricStatisticsOptions(); return options.unit(unit); } + } + } diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptionsV2.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptionsV2.java new file mode 100644 index 0000000000..253debb416 --- /dev/null +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/GetMetricStatisticsOptionsV2.java @@ -0,0 +1,371 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.cloudwatch.options; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import org.jclouds.cloudwatch.domain.Dimension; +import org.jclouds.cloudwatch.domain.Statistics; +import org.jclouds.cloudwatch.domain.Unit; +import org.jclouds.date.DateService; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.options.BaseHttpRequestOptions; +import org.jclouds.javax.annotation.Nullable; + +import java.util.Date; +import java.util.Set; + +/** + * Options use to get statistics for the specified metric. + * + * @see + * + * @author Jeremy Whitlock + */ +public class GetMetricStatisticsOptionsV2 extends BaseHttpRequestOptions { + + private static final DateService dateService = new SimpleDateFormatDateService(); + private final Set dimensions; + private final Date endTime; + private final String metricName; + private final String namespace; + private final int period; + private final Date startTime; + private final Set statistics; + private final Unit unit; + + /** + * Private constructor to enforce using {@link Builder}. + */ + private GetMetricStatisticsOptionsV2(@Nullable Set dimensions, Date endTime, String metricName, + String namespace, int period, Date startTime, Set statistics, + Unit unit) { + this.dimensions = dimensions; + this.endTime = endTime; + this.metricName = metricName; + this.namespace = namespace; + this.period = period; + this.startTime = startTime; + this.statistics = statistics; + this.unit = unit; + } + + /** + * return the set of dimensions for this request + */ + @Nullable + public Set getDimensions() { + return dimensions; + } + + /** + * return the end time for this request + */ + public Date getEndTime() { + return endTime; + } + + /** + * return the metric name for this request + */ + public String getMetricName() { + return metricName; + } + + /** + * return the namespace for this request + */ + public String getNamespace() { + return namespace; + } + + /** + * return the period for this request + */ + public int getPeriod() { + return period; + } + + /** + * return the start time for this request + */ + public Date getStartTime() { + return startTime; + } + + /** + * return the statistics for this request + */ + public Set getStatistics() { + return statistics; + } + + /** + * return the unit for this request + */ + public Unit getUnit() { + return unit; + } + + /** + * Returns a new builder. The generated builder is equivalent to the builder + * created by the {@link Builder} constructor. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Set dimensions = Sets.newLinkedHashSet(); + private Date endTime; + private String metricName; + private String namespace; + private int period; + private Date startTime; + private Set statistics = Sets.newLinkedHashSet(); + private Unit unit; + + /** + * Creates a new builder. The returned builder is equivalent to the builder + * generated by {@link ListMetricsOptions#builder}. + */ + public Builder() {} + + /** + * A list of dimensions describing qualities of the metric. (Set can be 10 or less items.) + * + * @param dimensions the dimensions describing the qualities of the metric + * + * @return this {@code Builder} object + * + * @throws IllegalArgumentException if this is invoked more than 10 times + */ + public Builder dimensions(Set dimensions) { + if (dimensions != null) { + Preconditions.checkArgument(dimensions.size() <= 10, "dimensions can have 10 or fewer members."); + this.dimensions = dimensions; + } + return this; + } + + /** + * A dimension describing qualities of the metric. (Can be called multiple times up to a maximum of 10 times.) + * + * @param dimension the dimension describing the qualities of the metric + * + * @return this {@code Builder} object + * + * @throws IllegalArgumentException if the number of dimensions would be greater than 10 after adding + */ + public Builder dimension(Dimension dimension) { + if (dimension != null) { + Preconditions.checkArgument(dimensions.size() < 10, "dimension member maximum count of 10 exceeded."); + this.dimensions.add(dimension); + } + return this; + } + + /** + * The time stamp to use for determining the last datapoint to return. The value specified is exclusive so + * results will include datapoints up to the time stamp specified. (Should be called once. Subsequent calls + * will overwrite the previous value.) + * + * @param endTime the timestamp to use for determining the last datapoint to return + * + * @return this {@code Builder} object + * + * @throws NullPointerException if endTime is null + */ + public Builder endTime(Date endTime) { + Preconditions.checkNotNull(endTime, "endTime cannot be null."); + this.endTime = endTime; + return this; + } + + /** + * The name of the metric. (Should be called once. Subsequent calls will overwrite the previous value.) + * + * @param metricName the metric name to filter against + * + * @return this {@code Builder} object + * + * @throws NullPointerException if metricName is null + * @throws IllegalArgumentException if metricName is empty + */ + public Builder metricName(String metricName) { + Preconditions.checkNotNull(metricName, "metricName cannot be null."); + Preconditions.checkArgument(metricName.length() > 1, "metricName must not be empty."); + this.metricName = metricName; + return this; + } + + /** + * The namespace of the metric. (Should be called once. Subsequent calls will overwrite the previous value. + * See {@link org.jclouds.cloudwatch.domain.Namespaces} for the known list of namespaces.) + * + * @param namespace the namespace to filter against + * + * @return this {@code Builder} object + * + * @throws NullPointerException if namespace is null + * @throws IllegalArgumentException if namespace is empty + */ + public Builder namespace(String namespace) { + Preconditions.checkNotNull(namespace, "namespace cannot be null."); + Preconditions.checkArgument(namespace.length() > 1, "namespace must not be empty."); + this.namespace = namespace; + return this; + } + + /** + * The granularity, in seconds, of the returned datapoints. Period must be at least 60 seconds and must be a + * multiple of 60. The default value is 60. (Should be called once. Subsequent calls will overwrite the + * previous value.) + * + * @param period the granularity, in seconds, of the returned datapoints + * + * @return this {@code Builder} object + * + * @throws NullPointerException if period is null + * @throws IllegalArgumentException if period is less than 60 or not a multiple of 60 + */ + public Builder period(int period) { + Preconditions.checkNotNull(period, "period cannot be null."); + Preconditions.checkArgument(period >= 60 && period % 60 == 0, "period must be greater than 60 and as a " + + "multiple of 60."); + this.period = period; + return this; + } + + /** + * The time stamp to use for determining the first datapoint to return. The value specified is inclusive so + * results include datapoints with the time stamp specified. (Should be called once. Subsequent calls will + * overwrite the previous value.) + * + * @param startTime The time stamp to use for determining the first datapoint to return + * + * @return this {@code Builder} object + * + * @throws NullPointerException if startTime is null + */ + public Builder startTime(Date startTime) { + Preconditions.checkNotNull(startTime, "startTime cannot be null."); + this.startTime = startTime; + return this; + } + + /** + * The metric statistics to return. (Should be called once. Subsequent calls will overwrite the previous + * value.) + * + * @param statistics the metric statistics to return. + * + * @return this {@code Builder} object + * + * @throws IllegalArgumentException if the number of statistics is greater than 5 + * @throws NullPointerException if statistics is null + */ + public Builder statistics(Set statistics) { + Preconditions.checkNotNull(statistics, "statistics cannot be null."); + Preconditions.checkArgument(statistics.size() <= 5, "statistics can have 5 or fewer members."); + this.statistics = statistics; + return this; + } + + /** + * The metric statistic to return. (Can be called multiple times up to a maximum of 5 times.) + * + * @param statistic the metric statistic to return + * + * @return this {@code Builder} object + * + * @throws IllegalArgumentException if the number of statistics would be greater than 5 after adding + * @throws NullPointerException if statistic is null + */ + public Builder statistic(Statistics statistic) { + Preconditions.checkNotNull(statistic, "statistic cannot be null."); + Preconditions.checkArgument(statistics.size() < 5, "statistics member maximum count of 5 exceeded."); + this.statistics.add(statistic); + return this; + } + + /** + * The unit for the metric. (Should be called once. Subsequent calls will overwrite the previous value.) + * + * @param unit the unit for the metric + * + * @return this {@code Builder} object + * + * @throws NullPointerException if unit is null + */ + public Builder unit(Unit unit) { + Preconditions.checkNotNull(unit, "unit cannot be null."); + this.unit = unit; + return this; + } + + /** + * Returns a newly-created {@code GetMetricStatisticsOptionsV2} based on the contents of + * the {@code Builder}. + * + * @throws NullPointerException if any of the required fields are null + * @throws IllegalArgumentException if any of the provided fields don't meet required criteria + */ + public GetMetricStatisticsOptionsV2 build() { + Preconditions.checkNotNull(endTime, "endTime cannot be null."); + Preconditions.checkNotNull(metricName, "metricName cannot be null."); + Preconditions.checkNotNull(namespace, "namespace cannot be null."); + Preconditions.checkNotNull(period, "period cannot be null."); + Preconditions.checkNotNull(startTime, "startTime cannot be null."); + Preconditions.checkNotNull(statistics, "statistics cannot be null."); + Preconditions.checkNotNull(unit, "unit cannot be null."); + Preconditions.checkArgument(statistics.size() >= 1, "statistics must have at least one member"); + + GetMetricStatisticsOptionsV2 options = new GetMetricStatisticsOptionsV2(dimensions, endTime, metricName, + namespace, period, startTime, + statistics, unit); + int dimensionIndex = 1; + int statisticIndex = 1; + + for (Dimension dimension : dimensions) { + options.formParameters.put("Dimensions.member." + dimensionIndex + ".Name", dimension.getName()); + options.formParameters.put("Dimensions.member." + dimensionIndex + ".Value", dimension.getValue()); + dimensionIndex++; + } + + options.formParameters.put("EndTime", dateService.iso8601SecondsDateFormat(endTime)); + options.formParameters.put("MetricName", metricName); + options.formParameters.put("Namespace", namespace); + options.formParameters.put("Period", Integer.toString(period)); + options.formParameters.put("StartTime", dateService.iso8601SecondsDateFormat(startTime)); + + for (Statistics statistic : statistics) { + options.formParameters.put("Statistics.member." + statisticIndex, statistic.toString()); + statisticIndex++; + } + + options.formParameters.put("Unit", unit.toString()); + + return options; + } + + } + +} diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/ListMetricsOptions.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/ListMetricsOptions.java index 1c2c7df580..ee4b024410 100644 --- a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/ListMetricsOptions.java +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/options/ListMetricsOptions.java @@ -18,11 +18,12 @@ */ package org.jclouds.cloudwatch.options; +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; import org.jclouds.cloudwatch.domain.Dimension; import org.jclouds.http.options.BaseHttpRequestOptions; import org.jclouds.javax.annotation.Nullable; -import java.util.LinkedHashSet; import java.util.Set; /** @@ -92,7 +93,7 @@ public class ListMetricsOptions extends BaseHttpRequestOptions { public static class Builder { - private Set dimensions = new LinkedHashSet(); + private Set dimensions = Sets.newLinkedHashSet(); private String metricName; private String namespace; private String nextToken; @@ -104,58 +105,62 @@ public class ListMetricsOptions extends BaseHttpRequestOptions { public Builder() {} /** - * Filter available metrics specific to the namespace provided. To get all metrics regardless of the namespace, - * do not use this builder option. Only one namespace can be specified at a time so multiple invocations of - * this will just overwrite what was set prior. Available AWS namespaces are listed in the - * {@link org.jclouds.cloudwatch.domain.Namespaces} class. You can also use custom namespaces if you've - * configured CloudWatch for such things. + * The namespace to filter against. (Should be called once. Subsequent calls will overwrite the previous value. + * See {@link org.jclouds.cloudwatch.domain.Namespaces} for the known list of namespaces.) * - * @param namespace the namespace to filter the returned metrics by + * @param namespace the namespace to filter against * * @return this {@code Builder} object + * + * @throws IllegalArgumentException if namespace is empty */ public Builder namespace(String namespace) { + if (namespace != null) { + Preconditions.checkArgument(namespace.length() > 1, "namespace must not be empty."); + } this.namespace = namespace; return this; } /** - * Filter available metrics specific to the provided metric name. To get all metrics regardless of the metric - * name, do not use this builder option. Only one namespace can be specified at a time so multiple invocations - * of this will just overwrite what was set prior. Available AWS metric names are listed in the - * org.jclouds.cloudwatch.domain.*Constants classes and are AWS product specific. You can also use custom - * metric names if you've configured CloudWatch for such things. + * The name of the metric to filter against. (Should be called once. Subsequent calls will overwrite the + * previous value.) * - * @param metricName the metric name to filter the returned metrics by + * @param metricName the metric name to filter against * * @return this {@code Builder} object + * + * @throws IllegalArgumentException if metricName is empty */ public Builder metricName(String metricName) { + if (metricName != null) { + Preconditions.checkArgument(metricName.length() > 1, "metricName must not be empty."); + } this.metricName = metricName; return this; } /** - * Same as {@link #dimension(org.jclouds.cloudwatch.domain.Dimension)} but replaces the current set of - * dimensions with the one passed in. + * A list of dimensions to filter against. (Set can be 10 or less items.) * - * @see #dimension(org.jclouds.cloudwatch.domain.Dimension) + * @param dimensions the dimensions to filter against + * + * @return this {@code Builder} object + * + * @throws IllegalArgumentException if the number of dimensions would be greater than 10 after adding */ public Builder dimensions(Set dimensions) { - if (dimensions.size() > 10) { - throw new IllegalArgumentException("The maximum number of dimensions for ListOptions is 10."); + if (dimensions != null) { + Preconditions.checkArgument(dimensions.size() <= 10, "dimensions can have 10 or fewer members."); + this.dimensions = dimensions; } - this.dimensions = dimensions; return this; } /** - * Filter available metrics based on the {@link Dimension} passed in. To get all metrics regardless of the - * dimension, do not use this builder option. You can specify up to 10 different dimensions per request. - * Available AWS dimensions are listed in the org.jclouds.cloudwatch.domain.*Constants classes and are AWS - * product specific. You can also use custom dimensions if you've configured CloudWatch for such things. + * A dimension to filter the available metrics by. (Can be called multiple times up to a maximum of 10 times.) * - * @param dimension the dimension to filter the returned metrics by + * @param dimension a dimension to filter the returned metrics by * * @return this {@code Builder} object * @@ -163,19 +168,16 @@ public class ListMetricsOptions extends BaseHttpRequestOptions { */ public Builder dimension(Dimension dimension) { if (dimension != null) { - if (this.dimensions.size() >= 10) { - throw new IllegalArgumentException("You have exceeded the maximum number of dimensions per " + - "request."); - } + Preconditions.checkArgument(dimensions.size() < 10, "dimension member maximum count of 10 exceeded."); this.dimensions.add(dimension); } return this; } /** - * Specify that this request is a follow-up to retrieve more data from a paginated request. + * The token returned by a previous call to indicate that there is more data available. * - * @param nextToken the next token + * @param nextToken the next token indicating that there is more data available * * @return this {@code Builder} object */ @@ -192,22 +194,22 @@ public class ListMetricsOptions extends BaseHttpRequestOptions { ListMetricsOptions lmo = new ListMetricsOptions(namespace, metricName, dimensions, nextToken); int dimensionIndex = 1; - if (this.namespace != null) { - lmo.formParameters.put("Namespace", this.namespace); + if (namespace != null) { + lmo.formParameters.put("Namespace", namespace); } - if (this.metricName != null) { - lmo.formParameters.put("MetricName", this.metricName); + if (metricName != null) { + lmo.formParameters.put("MetricName", metricName); } - for (Dimension dimension : this.dimensions) { + for (Dimension dimension : dimensions) { lmo.formParameters.put("Dimensions.member." + dimensionIndex + ".Name", dimension.getName()); lmo.formParameters.put("Dimensions.member." + dimensionIndex + ".Value", dimension.getValue()); dimensionIndex++; } - if (this.nextToken != null) { - lmo.formParameters.put("NextToken", this.nextToken); + if (nextToken != null) { + lmo.formParameters.put("NextToken", nextToken); } return lmo; diff --git a/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/xml/GetMetricStatisticsResponseHandlerV2.java b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/xml/GetMetricStatisticsResponseHandlerV2.java new file mode 100644 index 0000000000..bf3e71b60d --- /dev/null +++ b/apis/cloudwatch/src/main/java/org/jclouds/cloudwatch/xml/GetMetricStatisticsResponseHandlerV2.java @@ -0,0 +1,90 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.cloudwatch.xml; + +import com.google.common.collect.Sets; +import org.jclouds.cloudwatch.domain.Datapoint; +import org.jclouds.cloudwatch.domain.GetMetricStatisticsResponse; +import org.jclouds.http.functions.ParseSax; +import org.jclouds.util.SaxUtils; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import javax.inject.Inject; +import java.util.Set; + +/** + * @see + * + * @author Jeremy Whitlock + */ +public class GetMetricStatisticsResponseHandlerV2 extends ParseSax.HandlerWithResult { + + private StringBuilder currentText = new StringBuilder(); + private Set datapoints = Sets.newLinkedHashSet(); + private String label; + private final DatapointHandler datapointHandler; + private boolean inDatapoints; + + @Inject + public GetMetricStatisticsResponseHandlerV2(DatapointHandler DatapointHandler) { + this.datapointHandler = DatapointHandler; + } + + public GetMetricStatisticsResponse getResult() { + return new GetMetricStatisticsResponse(datapoints, label); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (qName.equals("Datapoints")) { + inDatapoints = true; + } + if (inDatapoints) { + datapointHandler.startElement(uri, localName, qName, attributes); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (inDatapoints) { + if (qName.equals("Datapoints")) { + inDatapoints = false; + } else { + datapointHandler.endElement(uri, localName, qName); + if (qName.equals("member")) { + this.datapoints.add(datapointHandler.getResult()); + } + } + } else if (qName.equals("Label")) { + label = SaxUtils.currentOrNull(currentText); + } + + currentText = new StringBuilder(); + } + + public void characters(char ch[], int start, int length) { + if (inDatapoints) { + datapointHandler.characters(ch, start, length); + } else { + currentText.append(ch, start, length); + } + } + +} diff --git a/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchAsyncClientTest.java b/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchAsyncClientTest.java index 22c820464f..9f8519b3e7 100644 --- a/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchAsyncClientTest.java +++ b/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchAsyncClientTest.java @@ -31,7 +31,9 @@ import org.jclouds.cloudwatch.domain.Dimension; import org.jclouds.cloudwatch.domain.EC2Constants; import org.jclouds.cloudwatch.domain.Namespaces; import org.jclouds.cloudwatch.domain.Statistics; +import org.jclouds.cloudwatch.domain.Unit; import org.jclouds.cloudwatch.options.GetMetricStatisticsOptions; +import org.jclouds.cloudwatch.options.GetMetricStatisticsOptionsV2; import org.jclouds.cloudwatch.options.ListMetricsOptions; import org.jclouds.cloudwatch.xml.GetMetricStatisticsResponseHandler; import org.jclouds.date.DateService; @@ -66,6 +68,59 @@ import static org.testng.Assert.assertEquals; @Test(groups = "unit", testName = "CloudWatchAsyncClientTest") public class CloudWatchAsyncClientTest extends BaseAsyncClientTest { + /** + * Tests that {@link CloudWatchAsyncClient#getMetricStatistics(org.jclouds.cloudwatch.options.GetMetricStatisticsOptionsV2)} + * works as expected. + * + * @throws Exception if anything goes wrong + */ + public void testGetMetricStatisticsV2() throws Exception { + Dimension dimension1 = new Dimension(EC2Constants.Dimension.INSTANCE_ID, "SOMEINSTANCEID"); + Dimension dimension2 = new Dimension(EC2Constants.Dimension.INSTANCE_TYPE, "t1.micro"); + Date endTime = new Date(10000000l); + String metricName = EC2Constants.MetricName.CPU_UTILIZATION; + String namespace = Namespaces.EC2; + int period = 60; + Date startTime = new Date(10000000l); + Statistics statistic1 = Statistics.MAXIMUM; + Statistics statistic2 = Statistics.MINIMUM; + Unit unit = Unit.PERCENT; + + GetMetricStatisticsOptionsV2 goodOptions = GetMetricStatisticsOptionsV2.builder() + .dimension(dimension1) + .dimension(dimension2) + .endTime(endTime) + .metricName(metricName) + .namespace(namespace) + .period(period) + .startTime(startTime) + .statistic(statistic1) + .statistic(statistic2) + .unit(unit).build(); + Method method = CloudWatchAsyncClient.class.getMethod("getMetricStatistics", GetMetricStatisticsOptionsV2.class); + HttpRequest request = processor.createRequest(method, goodOptions); + + assertRequestLineEquals(request, "POST https://monitoring.us-east-1.amazonaws.com/ HTTP/1.1"); + assertNonPayloadHeadersEqual(request, "Host: monitoring.us-east-1.amazonaws.com\n"); + + // Note: Order of request params is alphabetical + assertPayloadEquals(request, + "Action=GetMetricStatistics" + + "&Dimensions.member.1.Name=" + dimension1.getName() + + "&Dimensions.member.1.Value=" + dimension1.getValue() + + "&Dimensions.member.2.Name=" + dimension2.getName() + + "&Dimensions.member.2.Value=" + dimension2.getValue() + + "&EndTime=1970-01-01T02%3A46%3A40Z" + + "&MetricName=" + metricName + + "&Namespace=" + URLEncoder.encode(namespace, "UTF-8") + + "&Period=" + period + + "&StartTime=1970-01-01T02%3A46%3A40Z" + + "&Statistics.member.1=" + statistic1 + + "&Statistics.member.2=" + statistic2 + + "&Unit=" + unit, + "application/x-www-form-urlencoded", false); + } + /** * Tests that {@link CloudWatchAsyncClient#listMetrics(org.jclouds.cloudwatch.options.ListMetricsOptions)} works * as expected. diff --git a/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchClientLiveTest.java b/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchClientLiveTest.java index 19f7a0882b..b10c5ddd24 100644 --- a/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchClientLiveTest.java +++ b/apis/cloudwatch/src/test/java/org/jclouds/cloudwatch/CloudWatchClientLiveTest.java @@ -18,17 +18,20 @@ */ package org.jclouds.cloudwatch; +import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; import org.jclouds.apis.BaseContextLiveTest; import org.jclouds.cloudwatch.domain.Datapoint; import org.jclouds.cloudwatch.domain.Dimension; import org.jclouds.cloudwatch.domain.EC2Constants; +import org.jclouds.cloudwatch.domain.GetMetricStatisticsResponse; import org.jclouds.cloudwatch.domain.ListMetricsResponse; import org.jclouds.cloudwatch.domain.Metric; import org.jclouds.cloudwatch.domain.Namespaces; import org.jclouds.cloudwatch.domain.Statistics; import org.jclouds.cloudwatch.domain.Unit; import org.jclouds.cloudwatch.options.GetMetricStatisticsOptions; +import org.jclouds.cloudwatch.options.GetMetricStatisticsOptionsV2; import org.jclouds.cloudwatch.options.ListMetricsOptions; import org.jclouds.rest.RestContext; import org.testng.annotations.BeforeClass; @@ -61,6 +64,56 @@ public class CloudWatchClientLiveTest extends BaseContextLiveTest 0) { + for (Metric metric : metricsResponse.getMetrics()) { + Set dimensions = metric.getDimensions(); + boolean testRan = false; + + for (Dimension dimension : dimensions) { + Date endTime = new Date(); + Calendar cal = Calendar.getInstance(); + + cal.add(Calendar.MINUTE, -60 * 24); // 24 hours + + GetMetricStatisticsOptionsV2 options = + GetMetricStatisticsOptionsV2.builder() + .dimension(dimension) + .endTime(endTime) + .metricName(metric.getMetricName()) + .namespace(metric.getNamespace()) + .period(300) + .startTime(cal.getTime()) + .statistics(ImmutableSet.of(Statistics.MAXIMUM, + Statistics.MINIMUM)) + .unit(Unit.PERCENT).build(); + GetMetricStatisticsResponse response = client.getMetricStatistics(options); + + if (response.getDatapoints().size() > 0) { + checkNotNull(response.getLabel()); + + for (Datapoint datapoint : response.getDatapoints()) { + checkArgument(datapoint.getAverage() == null); + checkNotNull(datapoint.getMaximum()); + checkNotNull(datapoint.getMinimum()); + } + + testRan = true; + break; + } + } + + if (testRan) { + break; + } + } + } + } + @Test protected void testListMetrics() { ListMetricsResponse response;