diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py
index ac328def2..f9d571a23 100644
--- a/moto/cloudwatch/models.py
+++ b/moto/cloudwatch/models.py
@@ -1,4 +1,7 @@
+import json
+
from moto.core import BaseBackend, BaseModel
+from moto.core.exceptions import RESTError
import boto.ec2.cloudwatch
import datetime
@@ -35,9 +38,26 @@ class FakeAlarm(BaseModel):
self.ok_actions = ok_actions
self.insufficient_data_actions = insufficient_data_actions
self.unit = unit
- self.state_updated_timestamp = datetime.datetime.utcnow()
self.configuration_updated_timestamp = datetime.datetime.utcnow()
+ self.history = []
+
+ self.state_reason = ''
+ self.state_reason_data = '{}'
+ self.state = 'OK'
+ self.state_updated_timestamp = datetime.datetime.utcnow()
+
+ def update_state(self, reason, reason_data, state_value):
+ # History type, that then decides what the rest of the items are, can be one of ConfigurationUpdate | StateUpdate | Action
+ self.history.append(
+ ('StateUpdate', self.state_reason, self.state_reason_data, self.state, self.state_updated_timestamp)
+ )
+
+ self.state_reason = reason
+ self.state_reason_data = reason_data
+ self.state = state_value
+ self.state_updated_timestamp = datetime.datetime.utcnow()
+
class MetricDatum(BaseModel):
@@ -122,10 +142,8 @@ class CloudWatchBackend(BaseBackend):
if alarm.name in alarm_names
]
- def get_alarms_by_state_value(self, state):
- raise NotImplementedError(
- "DescribeAlarm by state is not implemented in moto."
- )
+ def get_alarms_by_state_value(self, target_state):
+ return filter(lambda alarm: alarm.state == target_state, self.alarms.values())
def delete_alarms(self, alarm_names):
for alarm_name in alarm_names:
@@ -164,6 +182,21 @@ class CloudWatchBackend(BaseBackend):
def get_dashboard(self, dashboard):
return self.dashboards.get(dashboard)
+ def set_alarm_state(self, alarm_name, reason, reason_data, state_value):
+ try:
+ if reason_data is not None:
+ json.loads(reason_data)
+ except ValueError:
+ raise RESTError('InvalidFormat', 'StateReasonData is invalid JSON')
+
+ if alarm_name not in self.alarms:
+ raise RESTError('ResourceNotFound', 'Alarm {0} not found'.format(alarm_name), status=404)
+
+ if state_value not in ('OK', 'ALARM', 'INSUFFICIENT_DATA'):
+ raise RESTError('InvalidParameterValue', 'StateValue is not one of OK | ALARM | INSUFFICIENT_DATA')
+
+ self.alarms[alarm_name].update_state(reason, reason_data, state_value)
+
class LogGroup(BaseModel):
diff --git a/moto/cloudwatch/responses.py b/moto/cloudwatch/responses.py
index cd7ce123e..7a5fa5ebd 100644
--- a/moto/cloudwatch/responses.py
+++ b/moto/cloudwatch/responses.py
@@ -1,4 +1,5 @@
import json
+from moto.core.utils import amzn_request_id
from moto.core.responses import BaseResponse
from .models import cloudwatch_backends
@@ -13,6 +14,7 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(ERROR_RESPONSE_TEMPLATE)
return template.render(code=code, message=message), dict(status=status)
+ @amzn_request_id
def put_metric_alarm(self):
name = self._get_param('AlarmName')
namespace = self._get_param('Namespace')
@@ -40,6 +42,7 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(PUT_METRIC_ALARM_TEMPLATE)
return template.render(alarm=alarm)
+ @amzn_request_id
def describe_alarms(self):
action_prefix = self._get_param('ActionPrefix')
alarm_name_prefix = self._get_param('AlarmNamePrefix')
@@ -62,12 +65,14 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(DESCRIBE_ALARMS_TEMPLATE)
return template.render(alarms=alarms)
+ @amzn_request_id
def delete_alarms(self):
alarm_names = self._get_multi_param('AlarmNames.member')
self.cloudwatch_backend.delete_alarms(alarm_names)
template = self.response_template(DELETE_METRIC_ALARMS_TEMPLATE)
return template.render()
+ @amzn_request_id
def put_metric_data(self):
namespace = self._get_param('Namespace')
metric_data = []
@@ -99,11 +104,13 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(PUT_METRIC_DATA_TEMPLATE)
return template.render()
+ @amzn_request_id
def list_metrics(self):
metrics = self.cloudwatch_backend.get_all_metrics()
template = self.response_template(LIST_METRICS_TEMPLATE)
return template.render(metrics=metrics)
+ @amzn_request_id
def delete_dashboards(self):
dashboards = self._get_multi_param('DashboardNames.member')
if dashboards is None:
@@ -116,18 +123,23 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(DELETE_DASHBOARD_TEMPLATE)
return template.render()
+ @amzn_request_id
def describe_alarm_history(self):
raise NotImplementedError()
+ @amzn_request_id
def describe_alarms_for_metric(self):
raise NotImplementedError()
+ @amzn_request_id
def disable_alarm_actions(self):
raise NotImplementedError()
+ @amzn_request_id
def enable_alarm_actions(self):
raise NotImplementedError()
+ @amzn_request_id
def get_dashboard(self):
dashboard_name = self._get_param('DashboardName')
@@ -138,9 +150,11 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(GET_DASHBOARD_TEMPLATE)
return template.render(dashboard=dashboard)
+ @amzn_request_id
def get_metric_statistics(self):
raise NotImplementedError()
+ @amzn_request_id
def list_dashboards(self):
prefix = self._get_param('DashboardNamePrefix', '')
@@ -149,6 +163,7 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(LIST_DASHBOARD_RESPONSE)
return template.render(dashboards=dashboards)
+ @amzn_request_id
def put_dashboard(self):
name = self._get_param('DashboardName')
body = self._get_param('DashboardBody')
@@ -163,14 +178,23 @@ class CloudWatchResponse(BaseResponse):
template = self.response_template(PUT_DASHBOARD_RESPONSE)
return template.render()
+ @amzn_request_id
def set_alarm_state(self):
- raise NotImplementedError()
+ alarm_name = self._get_param('AlarmName')
+ reason = self._get_param('StateReason')
+ reason_data = self._get_param('StateReasonData')
+ state_value = self._get_param('StateValue')
+
+ self.cloudwatch_backend.set_alarm_state(alarm_name, reason, reason_data, state_value)
+
+ template = self.response_template(SET_ALARM_STATE_TEMPLATE)
+ return template.render()
PUT_METRIC_ALARM_TEMPLATE = """
- 2690d7eb-ed86-11dd-9877-6fad448a8419
+ {{ request_id }}
"""
@@ -229,7 +253,7 @@ DESCRIBE_ALARMS_TEMPLATE = """
- 2690d7eb-ed86-11dd-9877-6fad448a8419
+ {{ request_id }}
"""
@@ -237,7 +261,7 @@ DELETE_METRIC_ALARMS_TEMPLATE = """
- 2690d7eb-ed86-11dd-9877-6fad448a8419
+ {{ request_id }}
"""
@@ -271,7 +295,7 @@ PUT_DASHBOARD_RESPONSE = """
- 68d1dc8c-9faa-11e7-a694-df2715690df2
+ {{ request_id }}
"""
@@ -307,16 +331,22 @@ GET_DASHBOARD_TEMPLATE = """
+
+ {{ request_id }}
+
+"""
+
ERROR_RESPONSE_TEMPLATE = """
Sender
{{ code }}
{{ message }}
- 5e45fd1e-9fa3-11e7-b720-89e8821d38c4
+ {{ request_id }}
"""
diff --git a/moto/core/utils.py b/moto/core/utils.py
index 2ea4dc4a8..43f05672e 100644
--- a/moto/core/utils.py
+++ b/moto/core/utils.py
@@ -272,9 +272,6 @@ def amzn_request_id(f):
else:
status, new_headers, body = response
headers.update(new_headers)
- # Cast status to string
- if "status" in headers:
- headers['status'] = str(headers['status'])
request_id = gen_amzn_requestid_long(headers)
diff --git a/tests/test_cloudwatch/test_cloudwatch.py b/tests/test_cloudwatch/test_cloudwatch.py
index 9b3f76c36..2f8528855 100644
--- a/tests/test_cloudwatch/test_cloudwatch.py
+++ b/tests/test_cloudwatch/test_cloudwatch.py
@@ -118,12 +118,3 @@ def test_describe_alarms():
alarms = conn.describe_alarms()
alarms.should.have.length_of(0)
-
-
-@mock_cloudwatch_deprecated
-def test_describe_state_value_unimplemented():
- conn = boto.connect_cloudwatch()
-
- conn.describe_alarms()
- conn.describe_alarms.when.called_with(
- state_value="foo").should.throw(NotImplementedError)
diff --git a/tests/test_cloudwatch/test_cloudwatch_boto3.py b/tests/test_cloudwatch/test_cloudwatch_boto3.py
index 923ba0b75..e621a642a 100644
--- a/tests/test_cloudwatch/test_cloudwatch_boto3.py
+++ b/tests/test_cloudwatch/test_cloudwatch_boto3.py
@@ -87,6 +87,54 @@ def test_get_dashboard_fail():
raise RuntimeError('Should of raised error')
+@mock_cloudwatch
+def test_alarm_state():
+ client = boto3.client('cloudwatch', region_name='eu-central-1')
+
+ client.put_metric_alarm(
+ AlarmName='testalarm1',
+ MetricName='cpu',
+ Namespace='blah',
+ Period=10,
+ EvaluationPeriods=5,
+ Statistic='Average',
+ Threshold=2,
+ ComparisonOperator='GreaterThanThreshold',
+ )
+ client.put_metric_alarm(
+ AlarmName='testalarm2',
+ MetricName='cpu',
+ Namespace='blah',
+ Period=10,
+ EvaluationPeriods=5,
+ Statistic='Average',
+ Threshold=2,
+ ComparisonOperator='GreaterThanThreshold',
+ )
+
+ # This is tested implicitly as if it doesnt work the rest will die
+ client.set_alarm_state(
+ AlarmName='testalarm1',
+ StateValue='ALARM',
+ StateReason='testreason',
+ StateReasonData='{"some": "json_data"}'
+ )
+
+ resp = client.describe_alarms(
+ StateValue='ALARM'
+ )
+ len(resp['MetricAlarms']).should.equal(1)
+ resp['MetricAlarms'][0]['AlarmName'].should.equal('testalarm1')
+
+ resp = client.describe_alarms(
+ StateValue='OK'
+ )
+ len(resp['MetricAlarms']).should.equal(1)
+ resp['MetricAlarms'][0]['AlarmName'].should.equal('testalarm2')
+
+ # Just for sanity
+ resp = client.describe_alarms()
+ len(resp['MetricAlarms']).should.equal(2)