Metric data query alarms (#3419)
* Add support for metric data query alarms (Metrics=[..]) * Fix trailing whitespace * Allow for unordered metrics in Python 2.7 * Add describe_alarm assertions and support DatapointsToAlarm
This commit is contained in:
parent
f8d2ce2e6a
commit
a3880c4c35
@ -31,6 +31,33 @@ class Dimension(object):
|
||||
return self != item
|
||||
|
||||
|
||||
class Metric(object):
|
||||
def __init__(self, metric_name, namespace, dimensions):
|
||||
self.metric_name = metric_name
|
||||
self.namespace = namespace
|
||||
self.dimensions = dimensions
|
||||
|
||||
|
||||
class MetricStat(object):
|
||||
def __init__(self, metric, period, stat, unit):
|
||||
self.metric = metric
|
||||
self.period = period
|
||||
self.stat = stat
|
||||
self.unit = unit
|
||||
|
||||
|
||||
class MetricDataQuery(object):
|
||||
def __init__(
|
||||
self, id, label, period, return_data, expression=None, metric_stat=None
|
||||
):
|
||||
self.id = id
|
||||
self.label = label
|
||||
self.period = period
|
||||
self.return_data = return_data
|
||||
self.expression = expression
|
||||
self.metric_stat = metric_stat
|
||||
|
||||
|
||||
def daterange(start, stop, step=timedelta(days=1), inclusive=False):
|
||||
"""
|
||||
This method will iterate from `start` to `stop` datetimes with a timedelta step of `step`
|
||||
@ -65,8 +92,10 @@ class FakeAlarm(BaseModel):
|
||||
name,
|
||||
namespace,
|
||||
metric_name,
|
||||
metric_data_queries,
|
||||
comparison_operator,
|
||||
evaluation_periods,
|
||||
datapoints_to_alarm,
|
||||
period,
|
||||
threshold,
|
||||
statistic,
|
||||
@ -81,8 +110,10 @@ class FakeAlarm(BaseModel):
|
||||
self.name = name
|
||||
self.namespace = namespace
|
||||
self.metric_name = metric_name
|
||||
self.metric_data_queries = metric_data_queries
|
||||
self.comparison_operator = comparison_operator
|
||||
self.evaluation_periods = evaluation_periods
|
||||
self.datapoints_to_alarm = datapoints_to_alarm
|
||||
self.period = period
|
||||
self.threshold = threshold
|
||||
self.statistic = statistic
|
||||
@ -235,8 +266,10 @@ class CloudWatchBackend(BaseBackend):
|
||||
name,
|
||||
namespace,
|
||||
metric_name,
|
||||
metric_data_queries,
|
||||
comparison_operator,
|
||||
evaluation_periods,
|
||||
datapoints_to_alarm,
|
||||
period,
|
||||
threshold,
|
||||
statistic,
|
||||
@ -252,8 +285,10 @@ class CloudWatchBackend(BaseBackend):
|
||||
name,
|
||||
namespace,
|
||||
metric_name,
|
||||
metric_data_queries,
|
||||
comparison_operator,
|
||||
evaluation_periods,
|
||||
datapoints_to_alarm,
|
||||
period,
|
||||
threshold,
|
||||
statistic,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import json
|
||||
from moto.core.utils import amzn_request_id
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import cloudwatch_backends
|
||||
from .models import cloudwatch_backends, MetricDataQuery, MetricStat, Metric, Dimension
|
||||
from dateutil.parser import parse as dtparse
|
||||
|
||||
|
||||
@ -19,8 +19,37 @@ class CloudWatchResponse(BaseResponse):
|
||||
name = self._get_param("AlarmName")
|
||||
namespace = self._get_param("Namespace")
|
||||
metric_name = self._get_param("MetricName")
|
||||
metrics = self._get_multi_param("Metrics.member")
|
||||
metric_data_queries = None
|
||||
if metrics:
|
||||
metric_data_queries = [
|
||||
MetricDataQuery(
|
||||
id=metric.get("Id"),
|
||||
label=metric.get("Label"),
|
||||
period=metric.get("Period"),
|
||||
return_data=metric.get("ReturnData"),
|
||||
expression=metric.get("Expression"),
|
||||
metric_stat=MetricStat(
|
||||
metric=Metric(
|
||||
metric_name=metric.get("MetricStat.Metric.MetricName"),
|
||||
namespace=metric.get("MetricStat.Metric.Namespace"),
|
||||
dimensions=[
|
||||
Dimension(name=dim["Name"], value=dim["Value"])
|
||||
for dim in metric["MetricStat.Metric.Dimensions.member"]
|
||||
],
|
||||
),
|
||||
period=metric.get("MetricStat.Period"),
|
||||
stat=metric.get("MetricStat.Stat"),
|
||||
unit=metric.get("MetricStat.Unit"),
|
||||
)
|
||||
if "MetricStat.Metric.MetricName" in metric
|
||||
else None,
|
||||
)
|
||||
for metric in metrics
|
||||
]
|
||||
comparison_operator = self._get_param("ComparisonOperator")
|
||||
evaluation_periods = self._get_param("EvaluationPeriods")
|
||||
datapoints_to_alarm = self._get_param("DatapointsToAlarm")
|
||||
period = self._get_param("Period")
|
||||
threshold = self._get_param("Threshold")
|
||||
statistic = self._get_param("Statistic")
|
||||
@ -37,8 +66,10 @@ class CloudWatchResponse(BaseResponse):
|
||||
name,
|
||||
namespace,
|
||||
metric_name,
|
||||
metric_data_queries,
|
||||
comparison_operator,
|
||||
evaluation_periods,
|
||||
datapoints_to_alarm,
|
||||
period,
|
||||
threshold,
|
||||
statistic,
|
||||
@ -261,35 +292,92 @@ DESCRIBE_ALARMS_TEMPLATE = """<DescribeAlarmsResponse xmlns="http://monitoring.a
|
||||
<AlarmDescription>{{ alarm.description }}</AlarmDescription>
|
||||
<AlarmName>{{ alarm.name }}</AlarmName>
|
||||
<ComparisonOperator>{{ alarm.comparison_operator }}</ComparisonOperator>
|
||||
<Dimensions>
|
||||
{% for dimension in alarm.dimensions %}
|
||||
<member>
|
||||
<Name>{{ dimension.name }}</Name>
|
||||
<Value>{{ dimension.value }}</Value>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Dimensions>
|
||||
{% if alarm.dimensions is not none %}
|
||||
<Dimensions>
|
||||
{% for dimension in alarm.dimensions %}
|
||||
<member>
|
||||
<Name>{{ dimension.name }}</Name>
|
||||
<Value>{{ dimension.value }}</Value>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Dimensions>
|
||||
{% endif %}
|
||||
<EvaluationPeriods>{{ alarm.evaluation_periods }}</EvaluationPeriods>
|
||||
{% if alarm.datapoints_to_alarm is not none %}
|
||||
<DatapointsToAlarm>{{ alarm.datapoints_to_alarm }}</DatapointsToAlarm>
|
||||
{% endif %}
|
||||
<InsufficientDataActions>
|
||||
{% for action in alarm.insufficient_data_actions %}
|
||||
<member>{{ action }}</member>
|
||||
{% endfor %}
|
||||
</InsufficientDataActions>
|
||||
{% if alarm.metric_name is not none %}
|
||||
<MetricName>{{ alarm.metric_name }}</MetricName>
|
||||
{% endif %}
|
||||
{% if alarm.metric_data_queries is not none %}
|
||||
<Metrics>
|
||||
{% for metric in alarm.metric_data_queries %}
|
||||
<member>
|
||||
<Id>{{ metric.id }}</Id>
|
||||
{% if metric.label is not none %}
|
||||
<Label>{{ metric.label }}</Label>
|
||||
{% endif %}
|
||||
{% if metric.expression is not none %}
|
||||
<Expression>{{ metric.expression }}</Expression>
|
||||
{% endif %}
|
||||
{% if metric.metric_stat is not none %}
|
||||
<MetricStat>
|
||||
<Metric>
|
||||
<Namespace>{{ metric.metric_stat.metric.namespace }}</Namespace>
|
||||
<MetricName>{{ metric.metric_stat.metric.metric_name }}</MetricName>
|
||||
<Dimensions>
|
||||
{% for dim in metric.metric_stat.metric.dimensions %}
|
||||
<member>
|
||||
<Name>{{ dim.name }}</Name>
|
||||
<Value>{{ dim.value }}</Value>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Dimensions>
|
||||
</Metric>
|
||||
{% if metric.metric_stat.period is not none %}
|
||||
<Period>{{ metric.metric_stat.period }}</Period>
|
||||
{% endif %}
|
||||
<Stat>{{ metric.metric_stat.stat }}</Stat>
|
||||
{% if metric.metric_stat.unit is not none %}
|
||||
<Unit>{{ metric.metric_stat.unit }}</Unit>
|
||||
{% endif %}
|
||||
</MetricStat>
|
||||
{% endif %}
|
||||
{% if metric.period is not none %}
|
||||
<Period>{{ metric.period }}</Period>
|
||||
{% endif %}
|
||||
<ReturnData>{{ metric.return_data }}</ReturnData>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Metrics>
|
||||
{% endif %}
|
||||
{% if alarm.namespace is not none %}
|
||||
<Namespace>{{ alarm.namespace }}</Namespace>
|
||||
{% endif %}
|
||||
<OKActions>
|
||||
{% for action in alarm.ok_actions %}
|
||||
<member>{{ action }}</member>
|
||||
{% endfor %}
|
||||
</OKActions>
|
||||
{% if alarm.period is not none %}
|
||||
<Period>{{ alarm.period }}</Period>
|
||||
{% endif %}
|
||||
<StateReason>{{ alarm.state_reason }}</StateReason>
|
||||
<StateReasonData>{{ alarm.state_reason_data }}</StateReasonData>
|
||||
<StateUpdatedTimestamp>{{ alarm.state_updated_timestamp }}</StateUpdatedTimestamp>
|
||||
<StateValue>{{ alarm.state_value }}</StateValue>
|
||||
{% if alarm.statistic is not none %}
|
||||
<Statistic>{{ alarm.statistic }}</Statistic>
|
||||
{% endif %}
|
||||
<Threshold>{{ alarm.threshold }}</Threshold>
|
||||
{% if alarm.unit is not none %}
|
||||
<Unit>{{ alarm.unit }}</Unit>
|
||||
{% endif %}
|
||||
</member>
|
||||
{% endfor %}
|
||||
</MetricAlarms>
|
||||
|
@ -141,6 +141,90 @@ def test_describe_alarms_for_metric():
|
||||
alarms.get("MetricAlarms").should.have.length_of(1)
|
||||
|
||||
|
||||
@mock_cloudwatch
|
||||
def test_describe_alarms():
|
||||
conn = boto3.client("cloudwatch", region_name="eu-central-1")
|
||||
conn.put_metric_alarm(
|
||||
AlarmName="testalarm1",
|
||||
MetricName="cpu",
|
||||
Namespace="blah",
|
||||
Period=10,
|
||||
EvaluationPeriods=5,
|
||||
Statistic="Average",
|
||||
Threshold=2,
|
||||
ComparisonOperator="GreaterThanThreshold",
|
||||
ActionsEnabled=True,
|
||||
)
|
||||
metric_data_queries = [
|
||||
{
|
||||
"Id": "metricA",
|
||||
"Expression": "metricB + metricC",
|
||||
"Label": "metricA",
|
||||
"ReturnData": True,
|
||||
},
|
||||
{
|
||||
"Id": "metricB",
|
||||
"MetricStat": {
|
||||
"Metric": {
|
||||
"Namespace": "ns",
|
||||
"MetricName": "metricB",
|
||||
"Dimensions": [{"Name": "Name", "Value": "B"}],
|
||||
},
|
||||
"Period": 60,
|
||||
"Stat": "Sum",
|
||||
},
|
||||
"ReturnData": False,
|
||||
},
|
||||
{
|
||||
"Id": "metricC",
|
||||
"MetricStat": {
|
||||
"Metric": {
|
||||
"Namespace": "AWS/Lambda",
|
||||
"MetricName": "metricC",
|
||||
"Dimensions": [{"Name": "Name", "Value": "C"}],
|
||||
},
|
||||
"Period": 60,
|
||||
"Stat": "Sum",
|
||||
"Unit": "Seconds",
|
||||
},
|
||||
"ReturnData": False,
|
||||
},
|
||||
]
|
||||
conn.put_metric_alarm(
|
||||
AlarmName="testalarm2",
|
||||
EvaluationPeriods=1,
|
||||
DatapointsToAlarm=1,
|
||||
Metrics=metric_data_queries,
|
||||
ComparisonOperator="GreaterThanThreshold",
|
||||
Threshold=1.0,
|
||||
)
|
||||
alarms = conn.describe_alarms()
|
||||
metric_alarms = alarms.get("MetricAlarms")
|
||||
metric_alarms.should.have.length_of(2)
|
||||
single_metric_alarm = [
|
||||
alarm for alarm in metric_alarms if alarm["AlarmName"] == "testalarm1"
|
||||
][0]
|
||||
multiple_metric_alarm = [
|
||||
alarm for alarm in metric_alarms if alarm["AlarmName"] == "testalarm2"
|
||||
][0]
|
||||
|
||||
single_metric_alarm["MetricName"].should.equal("cpu")
|
||||
single_metric_alarm.shouldnt.have.property("Metrics")
|
||||
single_metric_alarm["Namespace"].should.equal("blah")
|
||||
single_metric_alarm["Period"].should.equal(10)
|
||||
single_metric_alarm["EvaluationPeriods"].should.equal(5)
|
||||
single_metric_alarm["Statistic"].should.equal("Average")
|
||||
single_metric_alarm["ComparisonOperator"].should.equal("GreaterThanThreshold")
|
||||
single_metric_alarm["Threshold"].should.equal(2)
|
||||
|
||||
multiple_metric_alarm.shouldnt.have.property("MetricName")
|
||||
multiple_metric_alarm["EvaluationPeriods"].should.equal(1)
|
||||
multiple_metric_alarm["DatapointsToAlarm"].should.equal(1)
|
||||
multiple_metric_alarm["Metrics"].should.equal(metric_data_queries)
|
||||
multiple_metric_alarm["ComparisonOperator"].should.equal("GreaterThanThreshold")
|
||||
multiple_metric_alarm["Threshold"].should.equal(1.0)
|
||||
|
||||
|
||||
@mock_cloudwatch
|
||||
def test_alarm_state():
|
||||
client = boto3.client("cloudwatch", region_name="eu-central-1")
|
||||
|
Loading…
Reference in New Issue
Block a user