diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index 612c39ce8..c37949422 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -35,7 +35,9 @@ class Dimension(object): def __eq__(self, item): if isinstance(item, Dimension): - return self.name == item.name and self.value == item.value + return self.name == item.name and ( + self.value is None or item.value is None or self.value == item.value + ) return False def __ne__(self, item): # Only needed on Py2; Py3 defines it implicitly @@ -222,7 +224,8 @@ class MetricDatum(BaseModel): return False if dimensions and any( - Dimension(d["Name"], d["Value"]) not in self.dimensions for d in dimensions + Dimension(d["Name"], d.get("Value")) not in self.dimensions + for d in dimensions ): return False return True diff --git a/moto/cloudwatch/responses.py b/moto/cloudwatch/responses.py index c1c30a9bc..e67431f9f 100644 --- a/moto/cloudwatch/responses.py +++ b/moto/cloudwatch/responses.py @@ -212,7 +212,7 @@ class CloudWatchResponse(BaseResponse): def list_metrics(self): namespace = self._get_param("Namespace") metric_name = self._get_param("MetricName") - dimensions = self._get_multi_param("Dimensions.member") + dimensions = self._get_params().get("Dimensions", []) next_token = self._get_param("NextToken") next_token, metrics = self.cloudwatch_backend.list_metrics( next_token, namespace, metric_name, dimensions diff --git a/tests/test_cloudwatch/test_cloudwatch_boto3.py b/tests/test_cloudwatch/test_cloudwatch_boto3.py index 04ce052c5..7f0a436df 100644 --- a/tests/test_cloudwatch/test_cloudwatch_boto3.py +++ b/tests/test_cloudwatch/test_cloudwatch_boto3.py @@ -379,6 +379,25 @@ def test_list_metrics_paginated(): ) +@mock_cloudwatch +def test_list_metrics_without_value(): + cloudwatch = boto3.client("cloudwatch", "eu-west-1") + # Create some metrics to filter on + create_metrics_with_dimensions(cloudwatch, namespace="MyNamespace", data_points=3) + # Verify we can filter by namespace/metric name + res = cloudwatch.list_metrics(Namespace="MyNamespace")["Metrics"] + res.should.have.length_of(3) + # Verify we can filter by Dimension without value + results = cloudwatch.list_metrics( + Namespace="MyNamespace", MetricName="MyMetric", Dimensions=[{"Name": "D1"}] + )["Metrics"] + + results.should.have.length_of(1) + results[0]["Namespace"].should.equals("MyNamespace") + results[0]["MetricName"].should.equal("MyMetric") + results[0]["Dimensions"].should.equal([{"Name": "D1", "Value": "V1"}]) + + def create_metrics(cloudwatch, namespace, metrics=5, data_points=5): for i in range(0, metrics): metric_name = "metric" + str(i) @@ -389,6 +408,20 @@ def create_metrics(cloudwatch, namespace, metrics=5, data_points=5): ) +def create_metrics_with_dimensions(cloudwatch, namespace, data_points=5): + for j in range(0, data_points): + cloudwatch.put_metric_data( + Namespace=namespace, + MetricData=[ + { + "MetricName": "MyMetric", + "Dimensions": [{"Name": f"D{j}", "Value": f"V{j}"}], + "Unit": "Seconds", + } + ], + ) + + @mock_cloudwatch def test_get_metric_data_within_timeframe(): utc_now = datetime.now(tz=pytz.utc)