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
|
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):
|
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`
|
This method will iterate from `start` to `stop` datetimes with a timedelta step of `step`
|
||||||
@ -65,8 +92,10 @@ class FakeAlarm(BaseModel):
|
|||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
metric_name,
|
metric_name,
|
||||||
|
metric_data_queries,
|
||||||
comparison_operator,
|
comparison_operator,
|
||||||
evaluation_periods,
|
evaluation_periods,
|
||||||
|
datapoints_to_alarm,
|
||||||
period,
|
period,
|
||||||
threshold,
|
threshold,
|
||||||
statistic,
|
statistic,
|
||||||
@ -81,8 +110,10 @@ class FakeAlarm(BaseModel):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.metric_name = metric_name
|
self.metric_name = metric_name
|
||||||
|
self.metric_data_queries = metric_data_queries
|
||||||
self.comparison_operator = comparison_operator
|
self.comparison_operator = comparison_operator
|
||||||
self.evaluation_periods = evaluation_periods
|
self.evaluation_periods = evaluation_periods
|
||||||
|
self.datapoints_to_alarm = datapoints_to_alarm
|
||||||
self.period = period
|
self.period = period
|
||||||
self.threshold = threshold
|
self.threshold = threshold
|
||||||
self.statistic = statistic
|
self.statistic = statistic
|
||||||
@ -235,8 +266,10 @@ class CloudWatchBackend(BaseBackend):
|
|||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
metric_name,
|
metric_name,
|
||||||
|
metric_data_queries,
|
||||||
comparison_operator,
|
comparison_operator,
|
||||||
evaluation_periods,
|
evaluation_periods,
|
||||||
|
datapoints_to_alarm,
|
||||||
period,
|
period,
|
||||||
threshold,
|
threshold,
|
||||||
statistic,
|
statistic,
|
||||||
@ -252,8 +285,10 @@ class CloudWatchBackend(BaseBackend):
|
|||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
metric_name,
|
metric_name,
|
||||||
|
metric_data_queries,
|
||||||
comparison_operator,
|
comparison_operator,
|
||||||
evaluation_periods,
|
evaluation_periods,
|
||||||
|
datapoints_to_alarm,
|
||||||
period,
|
period,
|
||||||
threshold,
|
threshold,
|
||||||
statistic,
|
statistic,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from moto.core.utils import amzn_request_id
|
from moto.core.utils import amzn_request_id
|
||||||
from moto.core.responses import BaseResponse
|
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
|
from dateutil.parser import parse as dtparse
|
||||||
|
|
||||||
|
|
||||||
@ -19,8 +19,37 @@ class CloudWatchResponse(BaseResponse):
|
|||||||
name = self._get_param("AlarmName")
|
name = self._get_param("AlarmName")
|
||||||
namespace = self._get_param("Namespace")
|
namespace = self._get_param("Namespace")
|
||||||
metric_name = self._get_param("MetricName")
|
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")
|
comparison_operator = self._get_param("ComparisonOperator")
|
||||||
evaluation_periods = self._get_param("EvaluationPeriods")
|
evaluation_periods = self._get_param("EvaluationPeriods")
|
||||||
|
datapoints_to_alarm = self._get_param("DatapointsToAlarm")
|
||||||
period = self._get_param("Period")
|
period = self._get_param("Period")
|
||||||
threshold = self._get_param("Threshold")
|
threshold = self._get_param("Threshold")
|
||||||
statistic = self._get_param("Statistic")
|
statistic = self._get_param("Statistic")
|
||||||
@ -37,8 +66,10 @@ class CloudWatchResponse(BaseResponse):
|
|||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
metric_name,
|
metric_name,
|
||||||
|
metric_data_queries,
|
||||||
comparison_operator,
|
comparison_operator,
|
||||||
evaluation_periods,
|
evaluation_periods,
|
||||||
|
datapoints_to_alarm,
|
||||||
period,
|
period,
|
||||||
threshold,
|
threshold,
|
||||||
statistic,
|
statistic,
|
||||||
@ -261,35 +292,92 @@ DESCRIBE_ALARMS_TEMPLATE = """<DescribeAlarmsResponse xmlns="http://monitoring.a
|
|||||||
<AlarmDescription>{{ alarm.description }}</AlarmDescription>
|
<AlarmDescription>{{ alarm.description }}</AlarmDescription>
|
||||||
<AlarmName>{{ alarm.name }}</AlarmName>
|
<AlarmName>{{ alarm.name }}</AlarmName>
|
||||||
<ComparisonOperator>{{ alarm.comparison_operator }}</ComparisonOperator>
|
<ComparisonOperator>{{ alarm.comparison_operator }}</ComparisonOperator>
|
||||||
<Dimensions>
|
{% if alarm.dimensions is not none %}
|
||||||
{% for dimension in alarm.dimensions %}
|
<Dimensions>
|
||||||
<member>
|
{% for dimension in alarm.dimensions %}
|
||||||
<Name>{{ dimension.name }}</Name>
|
<member>
|
||||||
<Value>{{ dimension.value }}</Value>
|
<Name>{{ dimension.name }}</Name>
|
||||||
</member>
|
<Value>{{ dimension.value }}</Value>
|
||||||
{% endfor %}
|
</member>
|
||||||
</Dimensions>
|
{% endfor %}
|
||||||
|
</Dimensions>
|
||||||
|
{% endif %}
|
||||||
<EvaluationPeriods>{{ alarm.evaluation_periods }}</EvaluationPeriods>
|
<EvaluationPeriods>{{ alarm.evaluation_periods }}</EvaluationPeriods>
|
||||||
|
{% if alarm.datapoints_to_alarm is not none %}
|
||||||
|
<DatapointsToAlarm>{{ alarm.datapoints_to_alarm }}</DatapointsToAlarm>
|
||||||
|
{% endif %}
|
||||||
<InsufficientDataActions>
|
<InsufficientDataActions>
|
||||||
{% for action in alarm.insufficient_data_actions %}
|
{% for action in alarm.insufficient_data_actions %}
|
||||||
<member>{{ action }}</member>
|
<member>{{ action }}</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</InsufficientDataActions>
|
</InsufficientDataActions>
|
||||||
|
{% if alarm.metric_name is not none %}
|
||||||
<MetricName>{{ alarm.metric_name }}</MetricName>
|
<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>
|
<Namespace>{{ alarm.namespace }}</Namespace>
|
||||||
|
{% endif %}
|
||||||
<OKActions>
|
<OKActions>
|
||||||
{% for action in alarm.ok_actions %}
|
{% for action in alarm.ok_actions %}
|
||||||
<member>{{ action }}</member>
|
<member>{{ action }}</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</OKActions>
|
</OKActions>
|
||||||
|
{% if alarm.period is not none %}
|
||||||
<Period>{{ alarm.period }}</Period>
|
<Period>{{ alarm.period }}</Period>
|
||||||
|
{% endif %}
|
||||||
<StateReason>{{ alarm.state_reason }}</StateReason>
|
<StateReason>{{ alarm.state_reason }}</StateReason>
|
||||||
<StateReasonData>{{ alarm.state_reason_data }}</StateReasonData>
|
<StateReasonData>{{ alarm.state_reason_data }}</StateReasonData>
|
||||||
<StateUpdatedTimestamp>{{ alarm.state_updated_timestamp }}</StateUpdatedTimestamp>
|
<StateUpdatedTimestamp>{{ alarm.state_updated_timestamp }}</StateUpdatedTimestamp>
|
||||||
<StateValue>{{ alarm.state_value }}</StateValue>
|
<StateValue>{{ alarm.state_value }}</StateValue>
|
||||||
|
{% if alarm.statistic is not none %}
|
||||||
<Statistic>{{ alarm.statistic }}</Statistic>
|
<Statistic>{{ alarm.statistic }}</Statistic>
|
||||||
|
{% endif %}
|
||||||
<Threshold>{{ alarm.threshold }}</Threshold>
|
<Threshold>{{ alarm.threshold }}</Threshold>
|
||||||
|
{% if alarm.unit is not none %}
|
||||||
<Unit>{{ alarm.unit }}</Unit>
|
<Unit>{{ alarm.unit }}</Unit>
|
||||||
|
{% endif %}
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</MetricAlarms>
|
</MetricAlarms>
|
||||||
|
@ -141,6 +141,90 @@ def test_describe_alarms_for_metric():
|
|||||||
alarms.get("MetricAlarms").should.have.length_of(1)
|
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
|
@mock_cloudwatch
|
||||||
def test_alarm_state():
|
def test_alarm_state():
|
||||||
client = boto3.client("cloudwatch", region_name="eu-central-1")
|
client = boto3.client("cloudwatch", region_name="eu-central-1")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user