import boto3 import pytest from botocore.exceptions import ClientError from datetime import datetime, timedelta, timezone from dateutil.tz import tzutc from decimal import Decimal from freezegun import freeze_time from operator import itemgetter from uuid import uuid4 from moto import mock_cloudwatch, mock_s3 from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID @mock_cloudwatch def test_put_metric_data_no_dimensions(): conn = boto3.client("cloudwatch", region_name="us-east-1") conn.put_metric_data( Namespace="tester", MetricData=[dict(MetricName="metric", Value=1.5)] ) metrics = conn.list_metrics()["Metrics"] assert {"Namespace": "tester", "MetricName": "metric", "Dimensions": []} in metrics @mock_cloudwatch def test_put_metric_data_can_not_have_nan(): client = boto3.client("cloudwatch", region_name="us-west-2") utc_now = datetime.now(tz=timezone.utc) with pytest.raises(ClientError) as exc: client.put_metric_data( Namespace="mynamespace", MetricData=[ { "MetricName": "mymetric", "Timestamp": utc_now, "Value": Decimal("NaN"), "Unit": "Count", } ], ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterValue" assert ( err["Message"] == "The value NaN for parameter MetricData.member.1.Value is invalid." ) @mock_cloudwatch def test_put_metric_data_can_not_have_value_and_values(): client = boto3.client("cloudwatch", region_name="us-west-2") utc_now = datetime.now(tz=timezone.utc) with pytest.raises(ClientError) as exc: client.put_metric_data( Namespace="mynamespace", MetricData=[ { "MetricName": "mymetric", "Timestamp": utc_now, "Value": 1.5, "Values": [1.0, 10.0], "Unit": "Count", } ], ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterValue" assert ( err["Message"] == "The parameters MetricData.member.1.Value and MetricData.member.1.Values are mutually exclusive and you have specified both." ) @mock_cloudwatch def test_put_metric_data_can_not_have_and_values_mismatched_counts(): client = boto3.client("cloudwatch", region_name="us-west-2") utc_now = datetime.now(tz=timezone.utc) with pytest.raises(ClientError) as exc: client.put_metric_data( Namespace="mynamespace", MetricData=[ { "MetricName": "mymetric", "Timestamp": utc_now, "Values": [1.0, 10.0], "Counts": [2, 4, 1], "Unit": "Count", } ], ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterValue" assert ( err["Message"] == "The parameters MetricData.member.1.Values and MetricData.member.1.Counts must be of the same size." ) @mock_cloudwatch def test_put_metric_data_values_and_counts(): client = boto3.client("cloudwatch", region_name="us-west-2") utc_now = datetime.now(tz=timezone.utc) namespace = "values" metric = "mymetric" client.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": metric, "Timestamp": utc_now, "Values": [1.0, 10.0], "Counts": [2, 4], } ], ) stats = client.get_metric_statistics( Namespace=namespace, MetricName=metric, StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["SampleCount", "Sum", "Maximum"], ) datapoint = stats["Datapoints"][0] assert datapoint["SampleCount"] == 6.0 assert datapoint["Sum"] == 42.0 assert datapoint["Maximum"] == 10.0 @mock_cloudwatch def test_put_metric_data_values_without_counts(): client = boto3.client("cloudwatch", region_name="us-west-2") utc_now = datetime.now(tz=timezone.utc) namespace = "values" metric = "mymetric" client.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": metric, "Timestamp": utc_now, "Values": [1.0, 10.0, 23.45], } ], ) stats = client.get_metric_statistics( Namespace=namespace, MetricName=metric, StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["SampleCount", "Sum", "Maximum"], ) datapoint = stats["Datapoints"][0] assert datapoint["SampleCount"] == 3.0 assert datapoint["Sum"] == 34.45 assert datapoint["Maximum"] == 23.45 @mock_cloudwatch def test_put_metric_data_value_and_statistics(): conn = boto3.client("cloudwatch", region_name="us-east-1") with pytest.raises(ClientError) as exc: conn.put_metric_data( Namespace="statistics", MetricData=[ dict( MetricName="stats", Value=123.0, StatisticValues=dict( Sum=10.0, Maximum=9.0, Minimum=1.0, SampleCount=2 ), ) ], ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterCombination" assert ( err["Message"] == "The parameters MetricData.member.1.Value and MetricData.member.1.StatisticValues are mutually exclusive and you have specified both." ) @mock_cloudwatch def test_put_metric_data_with_statistics(): conn = boto3.client("cloudwatch", region_name="us-east-1") utc_now = datetime.now(tz=timezone.utc) conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName="statmetric", Timestamp=utc_now, # no Value to test https://github.com/getmoto/moto/issues/1615 StatisticValues=dict( SampleCount=3.0, Sum=123.0, Maximum=100.0, Minimum=12.0 ), Unit="Milliseconds", StorageResolution=123, ) ], ) metrics = conn.list_metrics()["Metrics"] assert { "Namespace": "tester", "MetricName": "statmetric", "Dimensions": [], } in metrics stats = conn.get_metric_statistics( Namespace="tester", MetricName="statmetric", StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["SampleCount", "Sum", "Maximum", "Minimum", "Average"], ) assert len(stats["Datapoints"]) == 1 datapoint = stats["Datapoints"][0] assert datapoint["SampleCount"] == 3.0 assert datapoint["Sum"] == 123.0 assert datapoint["Minimum"] == 12.0 assert datapoint["Maximum"] == 100.0 assert datapoint["Average"] == 41.0 # add single value conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName="statmetric", Timestamp=utc_now, Value=101.0, Unit="Milliseconds", ) ], ) # check stats again - should have changed, because there is one more datapoint stats = conn.get_metric_statistics( Namespace="tester", MetricName="statmetric", StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["SampleCount", "Sum", "Maximum", "Minimum", "Average"], ) assert len(stats["Datapoints"]) == 1 datapoint = stats["Datapoints"][0] assert datapoint["SampleCount"] == 4.0 assert datapoint["Sum"] == 224.0 assert datapoint["Minimum"] == 12.0 assert datapoint["Maximum"] == 101.0 assert datapoint["Average"] == 56.0 @mock_cloudwatch def test_get_metric_statistics(): conn = boto3.client("cloudwatch", region_name="us-east-1") utc_now = datetime.now(tz=timezone.utc) conn.put_metric_data( Namespace="tester", MetricData=[dict(MetricName="metric", Value=1.5, Timestamp=utc_now)], ) stats = conn.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["SampleCount", "Sum"], ) assert len(stats["Datapoints"]) == 1 datapoint = stats["Datapoints"][0] assert datapoint["SampleCount"] == 1.0 assert datapoint["Sum"] == 1.5 @mock_cloudwatch def test_get_metric_invalid_parameter_combination(): conn = boto3.client("cloudwatch", region_name="us-east-1") utc_now = datetime.now(tz=timezone.utc) conn.put_metric_data( Namespace="tester", MetricData=[dict(MetricName="metric", Value=1.5, Timestamp=utc_now)], ) with pytest.raises(ClientError) as exc: # make request without both statistics or extended statistics parameters conn.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterCombination" assert err["Message"] == "Must specify either Statistics or ExtendedStatistics" @mock_cloudwatch def test_get_metric_statistics_dimensions(): conn = boto3.client("cloudwatch", region_name="us-east-1") utc_now = datetime.now(tz=timezone.utc) # put metric data with different dimensions dimensions1 = [{"Name": "dim1", "Value": "v1"}] dimensions2 = dimensions1 + [{"Name": "dim2", "Value": "v2"}] metric_name = "metr-stats-dims" conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName=metric_name, Value=1, Timestamp=utc_now, Dimensions=dimensions1, ) ], ) conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName=metric_name, Value=2, Timestamp=utc_now, Dimensions=dimensions1, ) ], ) conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName=metric_name, Value=6, Timestamp=utc_now, Dimensions=dimensions2, ) ], ) # list of (, , ) params_list = ( # get metric stats with no restriction on dimensions ({}, 9, 3), # get metric stats for dimensions1 (should also cover dimensions2) ({"Dimensions": dimensions1}, 9, 3), # get metric stats for dimensions2 only ({"Dimensions": dimensions2}, 6, 6), ) for params in params_list: stats = conn.get_metric_statistics( Namespace="tester", MetricName=metric_name, StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["Average", "Sum"], **params[0], ) assert len(stats["Datapoints"]) == 1 datapoint = stats["Datapoints"][0] assert datapoint["Sum"] == params[1] assert datapoint["Average"] == params[2] @mock_cloudwatch def test_get_metric_statistics_endtime_sooner_than_starttime(): # given utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") # when with pytest.raises(ClientError) as e: # get_metric_statistics cloudwatch.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now + timedelta(seconds=1), EndTime=utc_now, Period=60, Statistics=["SampleCount"], ) # then ex = e.value assert ex.operation_name == "GetMetricStatistics" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 assert ex.response["Error"]["Code"] == "InvalidParameterValue" assert ( ex.response["Error"]["Message"] == "The parameter StartTime must be less than the parameter EndTime." ) @mock_cloudwatch def test_get_metric_statistics_starttime_endtime_equals(): # given utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") # when with pytest.raises(ClientError) as e: # get_metric_statistics cloudwatch.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now, EndTime=utc_now, Period=60, Statistics=["SampleCount"], ) # then ex = e.value assert ex.operation_name == "GetMetricStatistics" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 assert ex.response["Error"]["Code"] == "InvalidParameterValue" assert ( ex.response["Error"]["Message"] == "The parameter StartTime must be less than the parameter EndTime." ) @mock_cloudwatch def test_get_metric_statistics_starttime_endtime_within_1_second(): # given utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") # when with pytest.raises(ClientError) as e: # get_metric_statistics cloudwatch.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now.replace(microsecond=20 * 1000), EndTime=utc_now.replace(microsecond=987 * 1000), Period=60, Statistics=["SampleCount"], ) # then ex = e.value assert ex.operation_name == "GetMetricStatistics" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 assert ex.response["Error"]["Code"] == "InvalidParameterValue" assert ( ex.response["Error"]["Message"] == "The parameter StartTime must be less than the parameter EndTime." ) @mock_cloudwatch def test_get_metric_statistics_starttime_endtime_ignore_miliseconds(): cloudwatch = boto3.client("cloudwatch", region_name="us-east-1") utc_now = datetime.now(tz=timezone.utc) cloudwatch.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName="metric", Value=1.5, Timestamp=utc_now.replace(microsecond=200 * 1000), ) ], ) stats = cloudwatch.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now.replace(microsecond=999 * 1000), EndTime=utc_now + timedelta(seconds=1), Period=60, Statistics=["SampleCount", "Sum"], ) assert len(stats["Datapoints"]) == 1 datapoint = stats["Datapoints"][0] assert datapoint["SampleCount"] == 1.0 assert datapoint["Sum"] == 1.5 @mock_cloudwatch def test_duplicate_put_metric_data(): conn = boto3.client("cloudwatch", region_name="us-east-1") utc_now = datetime.now(tz=timezone.utc) conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName="metric", Dimensions=[{"Name": "Name", "Value": "B"}], Value=1.5, Timestamp=utc_now, ) ], ) result = conn.list_metrics( Namespace="tester", Dimensions=[{"Name": "Name", "Value": "B"}] )["Metrics"] assert len(result) == 1 conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName="metric", Dimensions=[{"Name": "Name", "Value": "B"}], Value=1.5, Timestamp=utc_now, ) ], ) result = conn.list_metrics( Namespace="tester", Dimensions=[{"Name": "Name", "Value": "B"}] )["Metrics"] assert len(result) == 1 assert result == [ { "Namespace": "tester", "MetricName": "metric", "Dimensions": [{"Name": "Name", "Value": "B"}], } ] conn.put_metric_data( Namespace="tester", MetricData=[ dict( MetricName="metric", Dimensions=[ {"Name": "Name", "Value": "B"}, {"Name": "Name", "Value": "C"}, ], Value=1.5, Timestamp=utc_now, ) ], ) result = conn.list_metrics( Namespace="tester", Dimensions=[{"Name": "Name", "Value": "B"}] )["Metrics"] assert result == [ { "Namespace": "tester", "MetricName": "metric", "Dimensions": [{"Name": "Name", "Value": "B"}], }, { "Namespace": "tester", "MetricName": "metric", "Dimensions": [ {"Name": "Name", "Value": "B"}, {"Name": "Name", "Value": "C"}, ], }, ] result = conn.list_metrics( Namespace="tester", Dimensions=[{"Name": "Name", "Value": "C"}] )["Metrics"] assert result == [ { "Namespace": "tester", "MetricName": "metric", "Dimensions": [ {"Name": "Name", "Value": "B"}, {"Name": "Name", "Value": "C"}, ], } ] @mock_cloudwatch @freeze_time("2020-02-10 18:44:05") def test_custom_timestamp(): utc_now = datetime.now(tz=timezone.utc) time = "2020-02-10T18:44:09Z" cw = boto3.client("cloudwatch", "eu-west-1") cw.put_metric_data( Namespace="tester", MetricData=[dict(MetricName="metric1", Value=1.5, Timestamp=time)], ) cw.put_metric_data( Namespace="tester", MetricData=[ dict(MetricName="metric2", Value=1.5, Timestamp=datetime(2020, 2, 10)) ], ) resp = cw.get_metric_statistics( Namespace="tester", MetricName="metric", StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), Period=60, Statistics=["SampleCount", "Sum"], ) assert resp["Datapoints"] == [] @mock_cloudwatch def test_list_metrics(): cloudwatch = boto3.client("cloudwatch", "eu-west-1") # Verify namespace has to exist res = cloudwatch.list_metrics(Namespace="unknown/")["Metrics"] assert res == [] # Create some metrics to filter on create_metrics(cloudwatch, namespace="list_test_1/", metrics=4, data_points=2) create_metrics(cloudwatch, namespace="list_test_2/", metrics=4, data_points=2) # Verify we can retrieve everything res = cloudwatch.list_metrics()["Metrics"] assert len(res) >= 16 # 2 namespaces * 4 metrics * 2 data points # Verify we can filter by namespace/metric name res = cloudwatch.list_metrics(Namespace="list_test_1/")["Metrics"] assert len(res) == 8 # 1 namespace * 4 metrics * 2 data points res = cloudwatch.list_metrics(Namespace="list_test_1/", MetricName="metric1")[ "Metrics" ] assert len(res) == 2 # 1 namespace * 1 metrics * 2 data points # Verify format assert res == [ {"Namespace": "list_test_1/", "Dimensions": [], "MetricName": "metric1"}, {"Namespace": "list_test_1/", "Dimensions": [], "MetricName": "metric1"}, ] # Verify unknown namespace still has no results res = cloudwatch.list_metrics(Namespace="unknown/")["Metrics"] assert res == [] @mock_cloudwatch def test_list_metrics_paginated(): cloudwatch = boto3.client("cloudwatch", "eu-west-1") # Verify that only a single page of metrics is returned assert "NextToken" not in cloudwatch.list_metrics() # Verify we can't pass a random NextToken with pytest.raises(ClientError) as e: cloudwatch.list_metrics(NextToken=str(uuid4())) assert ( e.value.response["Error"]["Message"] == "Request parameter NextToken is invalid" ) # Add a boatload of metrics create_metrics(cloudwatch, namespace="test", metrics=100, data_points=1) # Verify that a single page is returned until we've reached 500 first_page = cloudwatch.list_metrics(Namespace="test") assert len(first_page["Metrics"]) == 100 assert len(first_page["Metrics"]) == 100 create_metrics(cloudwatch, namespace="test", metrics=200, data_points=2) first_page = cloudwatch.list_metrics(Namespace="test") assert len(first_page["Metrics"]) == 500 assert "NextToken" not in first_page # Verify that adding more data points results in pagination create_metrics(cloudwatch, namespace="test", metrics=60, data_points=10) first_page = cloudwatch.list_metrics(Namespace="test") assert len(first_page["Metrics"]) == 500 # Retrieve second page - and verify there's more where that came from second_page = cloudwatch.list_metrics( Namespace="test", NextToken=first_page["NextToken"] ) assert len(second_page["Metrics"]) == 500 # Last page should only have the last 100 results, and no NextToken (indicating that pagination is finished) third_page = cloudwatch.list_metrics( Namespace="test", NextToken=second_page["NextToken"] ) assert len(third_page["Metrics"]) == 100 assert "NextToken" not in third_page # Verify that we can't reuse an existing token with pytest.raises(ClientError) as e: cloudwatch.list_metrics(Namespace="test", NextToken=first_page["NextToken"]) assert ( e.value.response["Error"]["Message"] == "Request parameter NextToken is invalid" ) @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"] assert len(res) == 3 # Verify we can filter by Dimension without value results = cloudwatch.list_metrics( Namespace="MyNamespace", MetricName="MyMetric", Dimensions=[{"Name": "D1"}] )["Metrics"] assert len(results) == 1 assert results[0]["Namespace"] == "MyNamespace" assert results[0]["MetricName"] == "MyMetric" assert results[0]["Dimensions"] == [{"Name": "D1", "Value": "V1"}] @mock_cloudwatch def test_list_metrics_with_same_dimensions_different_metric_name(): cloudwatch = boto3.client("cloudwatch", "eu-west-1") # create metrics with same namespace and dimensions but different metric names cloudwatch.put_metric_data( Namespace="unique/", MetricData=[ { "MetricName": "metric1", "Dimensions": [{"Name": "D1", "Value": "V1"}], "Unit": "Seconds", } ], ) cloudwatch.put_metric_data( Namespace="unique/", MetricData=[ { "MetricName": "metric2", "Dimensions": [{"Name": "D1", "Value": "V1"}], "Unit": "Seconds", } ], ) results = cloudwatch.list_metrics(Namespace="unique/")["Metrics"] assert len(results) == 2 # duplicating existing metric cloudwatch.put_metric_data( Namespace="unique/", MetricData=[ { "MetricName": "metric1", "Dimensions": [{"Name": "D1", "Value": "V1"}], "Unit": "Seconds", } ], ) # asserting only unique values are returned results = cloudwatch.list_metrics(Namespace="unique/")["Metrics"] assert len(results) == 2 def create_metrics(cloudwatch, namespace, metrics=5, data_points=5): for i in range(0, metrics): metric_name = "metric" + str(i) for j in range(0, data_points): cloudwatch.put_metric_data( Namespace=namespace, MetricData=[{"MetricName": metric_name, "Value": j, "Unit": "Seconds"}], ) 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_for_multiple_metrics_w_same_dimensions(): utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace = "my_namespace/" cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Dimensions": [{"Name": "Name", "Value": "B"}], "Value": 50, }, { "MetricName": "metric2", "Dimensions": [{"Name": "Name", "Value": "B"}], "Value": 25, "Unit": "Microseconds", }, ], ) # get_metric_data 1 response1 = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result1", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", "Dimensions": [{"Name": "Name", "Value": "B"}], }, "Period": 60, "Stat": "Sum", }, }, ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) # assert len(response1["MetricDataResults"]) == 1 res1 = response1["MetricDataResults"][0] assert res1["Values"] == [50.0] # get_metric_data 2 response2 = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result2", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric2", "Dimensions": [{"Name": "Name", "Value": "B"}], }, "Period": 60, "Stat": "Sum", }, }, ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) # assert len(response2["MetricDataResults"]) == 1 res2 = response2["MetricDataResults"][0] assert res2["Values"] == [25.0] @mock_cloudwatch def test_get_metric_data_within_timeframe(): utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace1 = "my_namespace/" # put metric data values = [0, 2, 4, 3.5, 7, 100] cloudwatch.put_metric_data( Namespace=namespace1, MetricData=[ {"MetricName": "metric1", "Value": val, "Unit": "Seconds"} for val in values ], ) # get_metric_data stats = ["Average", "Sum", "Minimum", "Maximum"] response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result_" + stat, "MetricStat": { "Metric": {"Namespace": namespace1, "MetricName": "metric1"}, "Period": 60, "Stat": stat, }, } for stat in stats ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) # # Assert Average/Min/Max/Sum is returned as expected avg = [ res for res in response["MetricDataResults"] if res["Id"] == "result_Average" ][0] assert avg["Label"] == "metric1 Average" assert avg["StatusCode"] == "Complete" assert [int(val) for val in avg["Values"]] == [19] sum_ = [res for res in response["MetricDataResults"] if res["Id"] == "result_Sum"][ 0 ] assert sum_["Label"] == "metric1 Sum" assert sum_["StatusCode"] == "Complete" assert [val for val in sum_["Values"]] == [sum(values)] min_ = [ res for res in response["MetricDataResults"] if res["Id"] == "result_Minimum" ][0] assert min_["Label"] == "metric1 Minimum" assert min_["StatusCode"] == "Complete" assert [int(val) for val in min_["Values"]] == [0] max_ = [ res for res in response["MetricDataResults"] if res["Id"] == "result_Maximum" ][0] assert max_["Label"] == "metric1 Maximum" assert max_["StatusCode"] == "Complete" assert [int(val) for val in max_["Values"]] == [100] @mock_cloudwatch def test_get_metric_data_partially_within_timeframe(): utc_now = datetime.now(tz=timezone.utc) yesterday = utc_now - timedelta(days=1) last_week = utc_now - timedelta(days=7) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace1 = "my_namespace/" # put metric data cloudwatch.put_metric_data( Namespace=namespace1, MetricData=[ { "MetricName": "metric1", "Value": 10, "Unit": "Seconds", "Timestamp": utc_now, } ], ) cloudwatch.put_metric_data( Namespace=namespace1, MetricData=[ { "MetricName": "metric1", "Value": 20, "Unit": "Seconds", "Timestamp": yesterday, } ], ) cloudwatch.put_metric_data( Namespace=namespace1, MetricData=[ { "MetricName": "metric1", "Value": 50, "Unit": "Seconds", "Timestamp": last_week, }, { "MetricName": "metric1", "Value": 10, "Unit": "Seconds", "Timestamp": last_week + timedelta(seconds=10), }, { "MetricName": "metric1", "Value": 20, "Unit": "Seconds", "Timestamp": last_week + timedelta(seconds=15), }, { "MetricName": "metric1", "Value": 40, "Unit": "Seconds", "Timestamp": last_week + timedelta(seconds=30), }, ], ) # data for average, min, max def get_data(start, end, stat="Sum", scanBy="TimestampAscending"): # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result", "MetricStat": { "Metric": {"Namespace": namespace1, "MetricName": "metric1"}, "Period": 60, "Stat": stat, }, } ], StartTime=start, EndTime=end, ScanBy=scanBy, ) return response response = get_data( start=yesterday - timedelta(seconds=60), end=utc_now + timedelta(seconds=60) ) # Assert Last week's data is not returned assert len(response["MetricDataResults"]) == 1 sum_ = response["MetricDataResults"][0] assert sum_["Label"] == "metric1 Sum" assert sum_["StatusCode"] == "Complete" assert sum_["Values"] == [20.0, 10.0] response = get_data( start=yesterday - timedelta(seconds=60), end=utc_now + timedelta(seconds=60), scanBy="TimestampDescending", ) assert response["MetricDataResults"][0]["Values"] == [10.0, 20.0] response = get_data( start=last_week - timedelta(seconds=1), end=utc_now + timedelta(seconds=60), stat="Average", ) # assert average assert response["MetricDataResults"][0]["Values"] == [30.0, 20.0, 10.0] response = get_data( start=last_week - timedelta(seconds=1), end=utc_now + timedelta(seconds=60), stat="Maximum", ) # assert maximum assert response["MetricDataResults"][0]["Values"] == [50.0, 20.0, 10.0] response = get_data( start=last_week - timedelta(seconds=1), end=utc_now + timedelta(seconds=60), stat="Minimum", ) # assert minimum assert response["MetricDataResults"][0]["Values"] == [10.0, 20.0, 10.0] @mock_cloudwatch def test_get_metric_data_outside_timeframe(): utc_now = datetime.now(tz=timezone.utc) last_week = utc_now - timedelta(days=7) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace1 = "my_namespace/" # put metric data cloudwatch.put_metric_data( Namespace=namespace1, MetricData=[ { "MetricName": "metric1", "Value": 50, "Unit": "Seconds", "Timestamp": last_week, } ], ) # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result", "MetricStat": { "Metric": {"Namespace": namespace1, "MetricName": "metric1"}, "Period": 60, "Stat": "Sum", }, } ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) # # Assert Last week's data is not returned assert len(response["MetricDataResults"]) == 1 assert response["MetricDataResults"][0]["Id"] == "result" assert response["MetricDataResults"][0]["StatusCode"] == "Complete" assert response["MetricDataResults"][0]["Values"] == [] @mock_cloudwatch def test_get_metric_data_for_multiple_metrics(): utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace = "my_namespace/" # put metric data cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Value": 50, "Unit": "Seconds", "Timestamp": utc_now, } ], ) cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric2", "Value": 25, "Unit": "Seconds", "Timestamp": utc_now, } ], ) # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result1", "MetricStat": { "Metric": {"Namespace": namespace, "MetricName": "metric1"}, "Period": 60, "Stat": "Sum", }, }, { "Id": "result2", "MetricStat": { "Metric": {"Namespace": namespace, "MetricName": "metric2"}, "Period": 60, "Stat": "Sum", }, }, ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) # assert len(response["MetricDataResults"]) == 2 res1 = [res for res in response["MetricDataResults"] if res["Id"] == "result1"][0] assert res1["Values"] == [50.0] res2 = [res for res in response["MetricDataResults"] if res["Id"] == "result2"][0] assert res2["Values"] == [25.0] @mock_cloudwatch def test_get_metric_data_for_dimensions(): utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace = "my_namespace/" # If the metric is created with multiple dimensions, then the data points for that metric can be retrieved only by specifying all the configured dimensions. # https://aws.amazon.com/premiumsupport/knowledge-center/cloudwatch-getmetricstatistics-data/ server_prod = {"Name": "Server", "Value": "Prod"} dimension_berlin = [server_prod, {"Name": "Domain", "Value": "Berlin"}] dimension_frankfurt = [server_prod, {"Name": "Domain", "Value": "Frankfurt"}] # put metric data cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Value": 50, "Dimensions": dimension_berlin, "Unit": "Seconds", "Timestamp": utc_now, } ], ) cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Value": 25, "Unit": "Seconds", "Dimensions": dimension_frankfurt, "Timestamp": utc_now, } ], ) # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result1", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", "Dimensions": dimension_frankfurt, }, "Period": 60, "Stat": "SampleCount", }, }, { "Id": "result2", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", "Dimensions": dimension_berlin, }, "Period": 60, "Stat": "Sum", }, }, { "Id": "result3", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", "Dimensions": [server_prod], }, "Period": 60, "Stat": "Sum", }, }, { "Id": "result4", "MetricStat": { "Metric": {"Namespace": namespace, "MetricName": "metric1"}, "Period": 60, "Stat": "Sum", }, }, ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) # assert len(response["MetricDataResults"]) == 4 res1 = [res for res in response["MetricDataResults"] if res["Id"] == "result1"][0] # expect sample count for dimension_frankfurt assert res1["Values"] == [1.0] res2 = [res for res in response["MetricDataResults"] if res["Id"] == "result2"][0] # expect sum for dimension_berlin assert res2["Values"] == [50.0] res3 = [res for res in response["MetricDataResults"] if res["Id"] == "result3"][0] # expect no result, as server_prod is only a part of other dimensions, e.g. there is no match assert res3["Values"] == [] res4 = [res for res in response["MetricDataResults"] if res["Id"] == "result4"][0] # expect sum of both metrics, as we did not filter for dimensions assert res4["Values"] == [75.0] @mock_cloudwatch def test_get_metric_data_for_unit(): utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace = "my_namespace/" unit = "Seconds" # put metric data cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Value": 50, "Unit": unit, "Timestamp": utc_now, }, { "MetricName": "metric1", "Value": -50, "Timestamp": utc_now, }, ], ) # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result_without_unit", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, { "Id": "result_with_unit", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", "Unit": unit, }, }, ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) expected_values = { "result_without_unit": 2.0, "result_with_unit": 1.0, } for id_, expected_value in expected_values.items(): metric_result_data = list( filter( lambda result_data: result_data["Id"] == id_, response["MetricDataResults"], ) ) assert len(metric_result_data) == 1 assert metric_result_data[0]["Values"][0] == expected_value @mock_cloudwatch def test_get_metric_data_endtime_sooner_than_starttime(): # given utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") # when with pytest.raises(ClientError) as e: # get_metric_data cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "test", "MetricStat": { "Metric": { "Namespace": "my_namespace/", "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, ], StartTime=utc_now + timedelta(seconds=1), EndTime=utc_now, ) # then ex = e.value assert ex.operation_name == "GetMetricData" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 err = ex.response["Error"] assert err["Code"] == "ValidationError" assert err["Message"] == "The parameter EndTime must be greater than StartTime." @mock_cloudwatch def test_get_metric_data_starttime_endtime_equals(): # given utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") # when with pytest.raises(ClientError) as e: # get_metric_data cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "test", "MetricStat": { "Metric": { "Namespace": "my_namespace/", "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, ], StartTime=utc_now, EndTime=utc_now, ) # then ex = e.value assert ex.operation_name == "GetMetricData" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 err = ex.response["Error"] assert err["Code"] == "ValidationError" assert err["Message"] == "The parameter StartTime must not equal parameter EndTime." @mock_cloudwatch def test_get_metric_data_starttime_endtime_within_1_second(): # given utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") # when with pytest.raises(ClientError) as e: # get_metric_data cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "test", "MetricStat": { "Metric": { "Namespace": "my_namespace/", "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, ], StartTime=utc_now.replace(microsecond=20 * 1000), EndTime=utc_now.replace(microsecond=987 * 1000), ) # then ex = e.value assert ex.operation_name == "GetMetricData" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 assert ex.response["Error"]["Code"] == "ValidationError" assert ( ex.response["Error"]["Message"] == "The parameter StartTime must not equal parameter EndTime." ) @mock_cloudwatch def test_get_metric_data_starttime_endtime_ignore_miliseconds(): utc_now = datetime.now(tz=timezone.utc).replace(microsecond=200 * 1000) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace = "my_namespace/" # put metric data cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Value": -50, "Timestamp": utc_now, }, ], ) # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "test", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, ], StartTime=utc_now.replace(microsecond=999 * 1000), EndTime=(utc_now + timedelta(seconds=1)).replace(microsecond=0), ) assert len(response["MetricDataResults"]) == 1 assert response["MetricDataResults"][0]["Id"] == "test" assert response["MetricDataResults"][0]["Values"][0] == 1.0 @mock_cloudwatch @mock_s3 def test_cloudwatch_return_s3_metrics(): utc_now = datetime.now(tz=timezone.utc) bucket_name = "examplebucket" cloudwatch = boto3.client("cloudwatch", "eu-west-3") # given s3 = boto3.resource("s3") s3_client = boto3.client("s3") bucket = s3.Bucket(bucket_name) bucket.create(CreateBucketConfiguration={"LocationConstraint": "eu-west-3"}) bucket.put_object(Body=b"ABCD", Key="file.txt") # when metrics = cloudwatch.list_metrics( Dimensions=[{"Name": "BucketName", "Value": bucket_name}] )["Metrics"] # then assert len(metrics) == 2 assert { "Namespace": "AWS/S3", "MetricName": "NumberOfObjects", "Dimensions": [ {"Name": "StorageType", "Value": "AllStorageTypes"}, {"Name": "BucketName", "Value": bucket_name}, ], } in metrics assert { "Namespace": "AWS/S3", "MetricName": "BucketSizeBytes", "Dimensions": [ {"Name": "StorageType", "Value": "StandardStorage"}, {"Name": "BucketName", "Value": bucket_name}, ], } in metrics # when stats = cloudwatch.get_metric_statistics( Namespace="AWS/S3", MetricName="BucketSizeBytes", Dimensions=[ {"Name": "BucketName", "Value": bucket_name}, {"Name": "StorageType", "Value": "StandardStorage"}, ], StartTime=utc_now - timedelta(days=2), EndTime=utc_now, Period=86400, Statistics=["Average"], Unit="Bytes", ) # then assert stats["Label"] == "BucketSizeBytes" assert len(stats["Datapoints"]) == 1 data_point = stats["Datapoints"][0] assert data_point["Average"] > 0 assert data_point["Unit"] == "Bytes" # when stats = cloudwatch.get_metric_statistics( Namespace="AWS/S3", MetricName="NumberOfObjects", Dimensions=[ {"Name": "BucketName", "Value": bucket_name}, {"Name": "StorageType", "Value": "AllStorageTypes"}, ], StartTime=utc_now - timedelta(days=2), EndTime=utc_now, Period=86400, Statistics=["Average"], ) # then assert stats["Label"] == "NumberOfObjects" assert len(stats["Datapoints"]) == 1 data_point = stats["Datapoints"][0] assert data_point["Average"] == 1 assert data_point["Unit"] == "Count" s3_client.delete_object(Bucket=bucket_name, Key="file.txt") s3_client.delete_bucket(Bucket=bucket_name) @mock_cloudwatch def test_put_metric_alarm(): # given region_name = "eu-central-1" client = boto3.client("cloudwatch", region_name=region_name) alarm_name = "test-alarm" sns_topic_arn = f"arn:aws:sns:${region_name}:${ACCOUNT_ID}:test-topic" # when client.put_metric_alarm( AlarmName=alarm_name, AlarmDescription="test alarm", ActionsEnabled=True, OKActions=[sns_topic_arn], AlarmActions=[sns_topic_arn], InsufficientDataActions=[sns_topic_arn], MetricName="5XXError", Namespace="AWS/ApiGateway", Statistic="Sum", Dimensions=[ {"Name": "ApiName", "Value": "test-api"}, {"Name": "Stage", "Value": "default"}, ], Period=60, Unit="Seconds", EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=1.0, ComparisonOperator="GreaterThanOrEqualToThreshold", TreatMissingData="notBreaching", Tags=[{"Key": "key-1", "Value": "value-1"}], ) # then alarms = client.describe_alarms(AlarmNames=[alarm_name])["MetricAlarms"] assert len(alarms) == 1 alarm = alarms[0] assert alarm["AlarmName"] == alarm_name assert ( alarm["AlarmArn"] == f"arn:aws:cloudwatch:{region_name}:{ACCOUNT_ID}:alarm:{alarm_name}" ) assert alarm["AlarmDescription"] == "test alarm" assert alarm["AlarmConfigurationUpdatedTimestamp"].tzinfo == tzutc() assert alarm["ActionsEnabled"] is True assert alarm["OKActions"] == [sns_topic_arn] assert alarm["AlarmActions"] == [sns_topic_arn] assert alarm["InsufficientDataActions"] == [sns_topic_arn] assert alarm["StateValue"] == "OK" assert alarm["StateReason"] == "Unchecked: Initial alarm creation" assert alarm["StateUpdatedTimestamp"].tzinfo == tzutc() assert alarm["MetricName"] == "5XXError" assert alarm["Namespace"] == "AWS/ApiGateway" assert alarm["Statistic"] == "Sum" assert sorted(alarm["Dimensions"], key=itemgetter("Name")) == [ {"Name": "ApiName", "Value": "test-api"}, {"Name": "Stage", "Value": "default"}, ] assert alarm["Period"] == 60 assert alarm["Unit"] == "Seconds" assert alarm["EvaluationPeriods"] == 1 assert alarm["DatapointsToAlarm"] == 1 assert alarm["Threshold"] == 1.0 assert alarm["ComparisonOperator"] == "GreaterThanOrEqualToThreshold" assert alarm["TreatMissingData"] == "notBreaching" @mock_cloudwatch def test_put_metric_alarm_with_percentile(): # given region_name = "eu-central-1" client = boto3.client("cloudwatch", region_name=region_name) alarm_name = "test-alarm" # when client.put_metric_alarm( AlarmName=alarm_name, AlarmDescription="test alarm", ActionsEnabled=True, MetricName="5XXError", Namespace="AWS/ApiGateway", ExtendedStatistic="p90", Dimensions=[ {"Name": "ApiName", "Value": "test-api"}, {"Name": "Stage", "Value": "default"}, ], Period=60, Unit="Seconds", EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=1.0, ComparisonOperator="GreaterThanOrEqualToThreshold", TreatMissingData="notBreaching", EvaluateLowSampleCountPercentile="ignore", ) # then alarms = client.describe_alarms(AlarmNames=[alarm_name])["MetricAlarms"] assert len(alarms) == 1 alarm = alarms[0] assert alarm["AlarmName"] == alarm_name assert ( alarm["AlarmArn"] == f"arn:aws:cloudwatch:{region_name}:{ACCOUNT_ID}:alarm:{alarm_name}" ) assert alarm["AlarmDescription"] == "test alarm" assert alarm["AlarmConfigurationUpdatedTimestamp"].tzinfo == tzutc() assert alarm["ActionsEnabled"] is True assert alarm["StateValue"] == "OK" assert alarm["StateReason"] == "Unchecked: Initial alarm creation" assert alarm["StateUpdatedTimestamp"].tzinfo == tzutc() assert alarm["MetricName"] == "5XXError" assert alarm["Namespace"] == "AWS/ApiGateway" assert alarm["ExtendedStatistic"] == "p90" assert sorted(alarm["Dimensions"], key=itemgetter("Name")) == [ {"Name": "ApiName", "Value": "test-api"}, {"Name": "Stage", "Value": "default"}, ] assert alarm["Period"] == 60 assert alarm["Unit"] == "Seconds" assert alarm["EvaluationPeriods"] == 1 assert alarm["DatapointsToAlarm"] == 1 assert alarm["Threshold"] == 1.0 assert alarm["ComparisonOperator"] == "GreaterThanOrEqualToThreshold" assert alarm["TreatMissingData"] == "notBreaching" assert alarm["EvaluateLowSampleCountPercentile"] == "ignore" @mock_cloudwatch def test_put_metric_alarm_with_anomaly_detection(): # given region_name = "eu-central-1" client = boto3.client("cloudwatch", region_name=region_name) alarm_name = "test-alarm" metrics = [ { "Id": "m1", "ReturnData": True, "MetricStat": { "Metric": { "MetricName": "CPUUtilization", "Namespace": "AWS/EC2", "Dimensions": [ {"Name": "instanceId", "Value": "i-1234567890abcdef0"} ], }, "Stat": "Average", "Period": 60, }, }, { "Id": "t1", "ReturnData": False, "Expression": "ANOMALY_DETECTION_BAND(m1, 3)", }, ] # when client.put_metric_alarm( AlarmName=alarm_name, ActionsEnabled=True, Metrics=metrics, EvaluationPeriods=2, ComparisonOperator="GreaterThanOrEqualToThreshold", ThresholdMetricId="t1", ) # then alarms = client.describe_alarms(AlarmNames=[alarm_name])["MetricAlarms"] assert len(alarms) == 1 alarm = alarms[0] assert alarm["AlarmName"] == alarm_name assert ( alarm["AlarmArn"] == f"arn:aws:cloudwatch:{region_name}:{ACCOUNT_ID}:alarm:{alarm_name}" ) assert alarm["AlarmConfigurationUpdatedTimestamp"].tzinfo == tzutc() assert alarm["StateValue"] == "OK" assert alarm["StateReason"] == "Unchecked: Initial alarm creation" assert alarm["StateUpdatedTimestamp"].tzinfo == tzutc() assert alarm["EvaluationPeriods"] == 2 assert alarm["ComparisonOperator"] == "GreaterThanOrEqualToThreshold" assert alarm["Metrics"] == metrics assert alarm["ThresholdMetricId"] == "t1" @mock_cloudwatch def test_put_metric_alarm_error_extended_statistic(): # given region_name = "eu-central-1" client = boto3.client("cloudwatch", region_name=region_name) alarm_name = "test-alarm" # when with pytest.raises(ClientError) as e: client.put_metric_alarm( AlarmName=alarm_name, ActionsEnabled=True, MetricName="5XXError", Namespace="AWS/ApiGateway", ExtendedStatistic="90", Dimensions=[ {"Name": "ApiName", "Value": "test-api"}, {"Name": "Stage", "Value": "default"}, ], Period=60, Unit="Seconds", EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=1.0, ComparisonOperator="GreaterThanOrEqualToThreshold", TreatMissingData="notBreaching", ) # then ex = e.value assert ex.operation_name == "PutMetricAlarm" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 assert ex.response["Error"]["Code"] == "InvalidParameterValue" assert ( ex.response["Error"]["Message"] == "The value 90 for parameter ExtendedStatistic is not supported." ) @mock_cloudwatch def test_put_metric_alarm_error_evaluate_low_sample_count_percentile(): # given region_name = "eu-central-1" client = boto3.client("cloudwatch", region_name=region_name) alarm_name = "test-alarm" # when with pytest.raises(ClientError) as e: client.put_metric_alarm( AlarmName=alarm_name, ActionsEnabled=True, MetricName="5XXError", Namespace="AWS/ApiGateway", ExtendedStatistic="p90", Dimensions=[ {"Name": "ApiName", "Value": "test-api"}, {"Name": "Stage", "Value": "default"}, ], Period=60, Unit="Seconds", EvaluationPeriods=1, DatapointsToAlarm=1, Threshold=1.0, ComparisonOperator="GreaterThanOrEqualToThreshold", TreatMissingData="notBreaching", EvaluateLowSampleCountPercentile="unknown", ) # then ex = e.value assert ex.operation_name == "PutMetricAlarm" assert ex.response["ResponseMetadata"]["HTTPStatusCode"] == 400 err = ex.response["Error"] assert err["Code"] == "ValidationError" assert ( err["Message"] == "Option unknown is not supported. " "Supported options for parameter EvaluateLowSampleCountPercentile are evaluate and ignore." ) @mock_cloudwatch def test_get_metric_data_with_custom_label(): utc_now = datetime.now(tz=timezone.utc) cloudwatch = boto3.client("cloudwatch", "eu-west-1") namespace = "my_namespace/" label = "MyCustomLabel" # put metric data cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ { "MetricName": "metric1", "Value": 50, "Timestamp": utc_now, }, { "MetricName": "metric1", "Value": -50, "Timestamp": utc_now, }, ], ) # get_metric_data response = cloudwatch.get_metric_data( MetricDataQueries=[ { "Id": "result_without_custom_label", "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, { "Id": "result_with_custom_label", "Label": label, "MetricStat": { "Metric": { "Namespace": namespace, "MetricName": "metric1", }, "Period": 60, "Stat": "SampleCount", }, }, ], StartTime=utc_now - timedelta(seconds=60), EndTime=utc_now + timedelta(seconds=60), ) expected_values = { "result_without_custom_label": "metric1 SampleCount", "result_with_custom_label": label, } for id_, expected_value in expected_values.items(): metric_result_data = list( filter( lambda result_data: result_data["Id"] == id_, response["MetricDataResults"], ) ) assert len(metric_result_data) == 1 assert metric_result_data[0]["Label"] == expected_value