diff --git a/moto/__init__.py b/moto/__init__.py
index fa1fa6ec0..df128c259 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -4,6 +4,7 @@ logging.getLogger('boto').setLevel(logging.CRITICAL)
from .autoscaling import mock_autoscaling
from .cloudformation import mock_cloudformation
+from .cloudwatch import mock_cloudwatch
from .dynamodb import mock_dynamodb
from .dynamodb2 import mock_dynamodb2
from .ec2 import mock_ec2
diff --git a/moto/backends.py b/moto/backends.py
index 6ee602ea5..3a50f5769 100644
--- a/moto/backends.py
+++ b/moto/backends.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
from moto.autoscaling import autoscaling_backend
+from moto.cloudwatch import cloudwatch_backend
from moto.dynamodb import dynamodb_backend
from moto.dynamodb2 import dynamodb_backend2
from moto.ec2 import ec2_backend
@@ -14,6 +15,7 @@ from moto.route53 import route53_backend
BACKENDS = {
'autoscaling': autoscaling_backend,
+ 'cloudwatch': cloudwatch_backend,
'dynamodb': dynamodb_backend,
'dynamodb2': dynamodb_backend2,
'ec2': ec2_backend,
diff --git a/moto/cloudwatch/__init__.py b/moto/cloudwatch/__init__.py
new file mode 100644
index 000000000..8ea4bf261
--- /dev/null
+++ b/moto/cloudwatch/__init__.py
@@ -0,0 +1,2 @@
+from .models import cloudwatch_backend
+mock_cloudwatch = cloudwatch_backend.decorator
diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py
new file mode 100644
index 000000000..43be48b36
--- /dev/null
+++ b/moto/cloudwatch/models.py
@@ -0,0 +1,50 @@
+from moto.core import BaseBackend
+
+
+class Dimension(object):
+ def __init__(self, name, value):
+ self.name = name
+ self.value = value
+
+
+class FakeAlarm(object):
+ def __init__(self, name, comparison_operator, evaluation_periods, period,
+ threshold, statistic, description, dimensions, alarm_actions,
+ ok_actions, insufficient_data_actions, unit):
+ self.name = name
+ self.comparison_operator = comparison_operator
+ self.evaluation_periods = evaluation_periods
+ self.period = period
+ self.threshold = threshold
+ self.statistic = statistic
+ self.description = description
+ self.dimensions = [Dimension(dimension['name'], dimension['value']) for dimension in dimensions]
+ self.alarm_actions = alarm_actions
+ self.ok_actions = ok_actions
+ self.insufficient_data_actions = insufficient_data_actions
+ self.unit = unit
+
+
+class CloudWatchBackend(BaseBackend):
+
+ def __init__(self):
+ self.alarms = {}
+
+ def put_metric_alarm(self, name, comparison_operator, evaluation_periods,
+ period, threshold, statistic, description, dimensions,
+ alarm_actions, ok_actions, insufficient_data_actions, unit):
+ alarm = FakeAlarm(name, comparison_operator, evaluation_periods, period,
+ threshold, statistic, description, dimensions, alarm_actions,
+ ok_actions, insufficient_data_actions, unit)
+ self.alarms[name] = alarm
+ return alarm
+
+ def get_all_alarms(self):
+ return self.alarms.values()
+
+ def delete_alarms(self, alarm_names):
+ for alarm_name in alarm_names:
+ self.alarms.pop(alarm_name, None)
+
+
+cloudwatch_backend = CloudWatchBackend()
diff --git a/moto/cloudwatch/responses.py b/moto/cloudwatch/responses.py
new file mode 100644
index 000000000..96fecfee6
--- /dev/null
+++ b/moto/cloudwatch/responses.py
@@ -0,0 +1,129 @@
+from jinja2 import Template
+
+from moto.core.responses import BaseResponse
+from moto.core.utils import camelcase_to_underscores
+from .models import cloudwatch_backend
+
+
+class CloudWatchResponse(BaseResponse):
+
+ def _get_param(self, param_name):
+ return self.querystring.get(param_name, [None])[0]
+
+ def _get_multi_param(self, param_prefix):
+ return [value[0] for key, value in self.querystring.items() if key.startswith(param_prefix)]
+
+ def _get_list_prefix(self, param_prefix):
+ results = []
+ param_index = 1
+ while True:
+ index_prefix = "{}.{}.".format(param_prefix, param_index)
+ new_items = {
+ camelcase_to_underscores(key.replace(index_prefix, "")): value[0]
+ for key, value in self.querystring.items()
+ if key.startswith(index_prefix)
+ }
+ if not new_items:
+ break
+ results.append(new_items)
+ param_index += 1
+ return results
+
+ def put_metric_alarm(self):
+ name = self._get_param('AlarmName')
+ comparison_operator = self._get_param('ComparisonOperator')
+ evaluation_periods = self._get_param('EvaluationPeriods')
+ period = self._get_param('Period')
+ threshold = self._get_param('Threshold')
+ statistic = self._get_param('Statistic')
+ description = self._get_param('AlarmDescription')
+ dimensions = self._get_list_prefix('Dimensions.member')
+ alarm_actions = self._get_multi_param('AlarmActions')
+ ok_actions = self._get_multi_param('OKActions')
+ insufficient_data_actions = self._get_multi_param("InsufficientDataActions")
+ unit = self._get_param('Unit')
+ alarm = cloudwatch_backend.put_metric_alarm(name, comparison_operator,
+ evaluation_periods, period,
+ threshold, statistic,
+ description, dimensions,
+ alarm_actions, ok_actions,
+ insufficient_data_actions,
+ unit)
+ template = Template(PUT_METRIC_ALARM_TEMPLATE)
+ return template.render(alarm=alarm)
+
+ def describe_alarms(self):
+ alarms = cloudwatch_backend.get_all_alarms()
+ template = Template(DESCRIBE_ALARMS_TEMPLATE)
+ return template.render(alarms=alarms)
+
+ def delete_alarms(self):
+ alarm_names = self._get_multi_param('AlarmNames.member')
+ cloudwatch_backend.delete_alarms(alarm_names)
+ template = Template(DELETE_METRIC_ALARMS_TEMPLATE)
+ return template.render()
+
+PUT_METRIC_ALARM_TEMPLATE = """
+
+
+ 2690d7eb-ed86-11dd-9877-6fad448a8419
+
+
+"""
+
+DESCRIBE_ALARMS_TEMPLATE = """
+
+ {% for alarm in alarms %}
+
+ {{ alarm.actions_enabled }}
+
+ {% for action in alarm.alarm_actions %}
+ {{ action }}
+ {% endfor %}
+
+ {{ alarm.arn }}
+ {{ alarm.configuration_updated_timestamp }}
+ {{ alarm.description }}
+ {{ alarm.name }}
+ {{ alarm.comparison_operator }}
+
+ {% for dimension in alarm.dimensions %}
+
+ {{ dimension.name }}
+ {{ dimension.value }}
+
+ {% endfor %}
+
+ {{ alarm.evaluation_periods }}
+
+ {% for action in alarm.insufficient_data_actions %}
+ {{ action }}
+ {% endfor %}
+
+ {{ alarm.metric_name }}
+ {{ alarm.namespace }}
+
+ {% for action in alarm.ok_actions %}
+ {{ action }}
+ {% endfor %}
+
+ {{ alarm.period }}
+ {{ alarm.state_reason }}
+ {{ alarm.state_reason_data }}
+ {{ alarm.state_updated_timestamp }}
+ {{ alarm.state_value }}
+ {{ alarm.statistic }}
+ {{ alarm.threshold }}
+ {{ alarm.unit }}
+
+ {% endfor %}
+
+"""
+
+DELETE_METRIC_ALARMS_TEMPLATE = """
+
+
+ 2690d7eb-ed86-11dd-9877-6fad448a8419
+
+
+"""
diff --git a/moto/cloudwatch/urls.py b/moto/cloudwatch/urls.py
new file mode 100644
index 000000000..2371f09cb
--- /dev/null
+++ b/moto/cloudwatch/urls.py
@@ -0,0 +1,9 @@
+from .responses import CloudWatchResponse
+
+url_bases = [
+ "https?://monitoring.(.+).amazonaws.com",
+]
+
+url_paths = {
+ '{0}/$': CloudWatchResponse().dispatch,
+}
diff --git a/tests/test_cloudwatch/test_cloudwatch.py b/tests/test_cloudwatch/test_cloudwatch.py
new file mode 100644
index 000000000..df3e41762
--- /dev/null
+++ b/tests/test_cloudwatch/test_cloudwatch.py
@@ -0,0 +1,71 @@
+import boto
+from boto.ec2.cloudwatch.alarm import MetricAlarm
+import sure # noqa
+
+from moto import mock_cloudwatch
+
+
+@mock_cloudwatch
+def test_create_alarm():
+ conn = boto.connect_cloudwatch()
+
+ alarm = MetricAlarm(
+ name='tester',
+ comparison='>=',
+ threshold=2.0,
+ period=60,
+ evaluation_periods=5,
+ statistic='Average',
+ description='A test',
+ dimensions={'InstanceId': ['i-0123456,i-0123457']},
+ alarm_actions=['arn:alarm'],
+ ok_actions=['arn:ok'],
+ insufficient_data_actions=['arn:insufficient'],
+ unit='Seconds',
+ )
+ conn.create_alarm(alarm)
+
+ alarms = conn.describe_alarms()
+ alarms.should.have.length_of(1)
+ alarm = alarms[0]
+ alarm.name.should.equal('tester')
+ alarm.comparison.should.equal('>=')
+ alarm.threshold.should.equal(2.0)
+ alarm.period.should.equal(60)
+ alarm.evaluation_periods.should.equal(5)
+ alarm.statistic.should.equal('Average')
+ alarm.description.should.equal('A test')
+ dict(alarm.dimensions).should.equal({'InstanceId': ['i-0123456,i-0123457']})
+ list(alarm.alarm_actions).should.equal(['arn:alarm'])
+ list(alarm.ok_actions).should.equal(['arn:ok'])
+ list(alarm.insufficient_data_actions).should.equal(['arn:insufficient'])
+ alarm.unit.should.equal('Seconds')
+
+
+@mock_cloudwatch
+def test_delete_alarm():
+ conn = boto.connect_cloudwatch()
+
+ alarm = MetricAlarm(
+ name='tester',
+ comparison='>=',
+ threshold=2.0,
+ period=60,
+ evaluation_periods=5,
+ statistic='Average',
+ description='A test',
+ dimensions={'InstanceId': ['i-0123456,i-0123457']},
+ alarm_actions=['arn:alarm'],
+ ok_actions=['arn:ok'],
+ insufficient_data_actions=['arn:insufficient'],
+ unit='Seconds',
+ )
+ conn.create_alarm(alarm)
+
+ alarms = conn.describe_alarms()
+ alarms.should.have.length_of(1)
+
+ alarms[0].delete()
+
+ alarms = conn.describe_alarms()
+ alarms.should.have.length_of(0)