diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py
index cb5283b99..ea8ca646b 100644
--- a/moto/cloudformation/parsing.py
+++ b/moto/cloudformation/parsing.py
@@ -37,6 +37,7 @@ MODEL_MAP = {
"AWS::RDS::DBInstance": rds_models.Database,
"AWS::RDS::DBSecurityGroup": rds_models.SecurityGroup,
"AWS::RDS::DBSubnetGroup": rds_models.SubnetGroup,
+ "AWS::Route53::HealthCheck": route53_models.HealthCheck,
"AWS::Route53::HostedZone": route53_models.FakeZone,
"AWS::Route53::RecordSet": route53_models.RecordSet,
"AWS::Route53::RecordSetGroup": route53_models.RecordSetGroup,
diff --git a/moto/route53/models.py b/moto/route53/models.py
index e6bb1c7a2..58c559f25 100644
--- a/moto/route53/models.py
+++ b/moto/route53/models.py
@@ -1,11 +1,65 @@
from __future__ import unicode_literals
+import uuid
from jinja2 import Template
from moto.core import BaseBackend
from moto.core.utils import get_random_hex
+class HealthCheck(object):
+ def __init__(self, health_check_id, health_check_args):
+ self.id = health_check_id
+ self.ip_address = health_check_args.get("ip_address")
+ self.port = health_check_args.get("port", 80)
+ self._type = health_check_args.get("type")
+ self.resource_path = health_check_args.get("resource_path")
+ self.fqdn = health_check_args.get("fqdn")
+ self.search_string = health_check_args.get("search_string")
+ self.request_interval = health_check_args.get("request_interval", 30)
+ self.failure_threshold = health_check_args.get("failure_threshold", 3)
+
+ @property
+ def physical_resource_id(self):
+ return self.id
+
+ @classmethod
+ def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
+ properties = cloudformation_json['Properties']['HealthCheckConfig']
+ health_check_args = {
+ "ip_address": properties.get('IPAddress'),
+ "port": properties.get('Port'),
+ "type": properties['Type'],
+ "resource_path": properties.get('ResourcePath'),
+ "fqdn": properties.get('FullyQualifiedDomainName'),
+ "search_string": properties.get('SearchString'),
+ "request_interval": properties.get('RequestInterval'),
+ "failure_threshold": properties.get('FailureThreshold'),
+ }
+ health_check = route53_backend.create_health_check(health_check_args)
+ return health_check
+
+ def to_xml(self):
+ template = Template("""
+ {{ health_check.id }}
+ example.com 192.0.2.17
+
+ {{ health_check.ip_address }}
+ {{ health_check.port }}
+ {{ health_check._type }}
+ {{ health_check.resource_path }}
+ {{ health_check.fqdn }}
+ {{ health_check.request_interval }}
+ {{ health_check.failure_threshold }}
+ {% if health_check.search_string %}
+ {{ health_check.search_string }}
+ {% endif %}
+
+ 1
+ """)
+ return template.render(health_check=self)
+
+
class RecordSet(object):
def __init__(self, kwargs):
self.name = kwargs.get('Name')
@@ -14,6 +68,7 @@ class RecordSet(object):
self.records = kwargs.get('ResourceRecords', [])
self.set_identifier = kwargs.get('SetIdentifier')
self.weight = kwargs.get('Weight')
+ self.health_check = kwargs.get('HealthCheckId')
@classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
@@ -42,7 +97,9 @@ class RecordSet(object):
{% endfor %}
-
+ {% if record_set.health_check %}
+ {{ record_set.health_check }}
+ {% endif %}
""")
return template.render(record_set=self)
@@ -111,6 +168,7 @@ class Route53Backend(BaseBackend):
def __init__(self):
self.zones = {}
+ self.health_checks = {}
def create_hosted_zone(self, name):
new_id = get_random_hex()
@@ -135,5 +193,16 @@ class Route53Backend(BaseBackend):
del self.zones[id_]
return zone
+ def create_health_check(self, health_check_args):
+ health_check_id = str(uuid.uuid4())
+ health_check = HealthCheck(health_check_id, health_check_args)
+ self.health_checks[health_check_id] = health_check
+ return health_check
+
+ def get_health_checks(self):
+ return self.health_checks.values()
+
+ def delete_health_check(self, health_check_id):
+ return self.health_checks.pop(health_check_id, None)
route53_backend = Route53Backend()
diff --git a/moto/route53/responses.py b/moto/route53/responses.py
index d110ebd55..5bbb8f451 100644
--- a/moto/route53/responses.py
+++ b/moto/route53/responses.py
@@ -70,6 +70,35 @@ def rrset_response(request, full_url, headers):
return 200, headers, template.render(record_sets=record_sets)
+def health_check_response(request, full_url, headers):
+ parsed_url = urlparse(full_url)
+ method = request.method
+
+ if method == "POST":
+ properties = xmltodict.parse(request.body)['CreateHealthCheckRequest']['HealthCheckConfig']
+ health_check_args = {
+ "ip_address": properties.get('IPAddress'),
+ "port": properties.get('Port'),
+ "type": properties['Type'],
+ "resource_path": properties.get('ResourcePath'),
+ "fqdn": properties.get('FullyQualifiedDomainName'),
+ "search_string": properties.get('SearchString'),
+ "request_interval": properties.get('RequestInterval'),
+ "failure_threshold": properties.get('FailureThreshold'),
+ }
+ health_check = route53_backend.create_health_check(health_check_args)
+ template = Template(CREATE_HEALTH_CHECK_RESPONSE)
+ return 201, headers, template.render(health_check=health_check)
+ elif method == "DELETE":
+ health_check_id = parsed_url.path.split("/")[-1]
+ route53_backend.delete_health_check(health_check_id)
+ return 200, headers, DELETE_HEALTH_CHECK_REPONSE
+ elif method == "GET":
+ template = Template(LIST_HEALTH_CHECKS_REPONSE)
+ health_checks = route53_backend.get_health_checks()
+ return 200, headers, template.render(health_checks=health_checks)
+
+
LIST_RRSET_REPONSE = """
{% for record_set in record_sets %}
@@ -126,3 +155,23 @@ LIST_HOSTED_ZONES_RESPONSE = """
+
+ {{ health_check.to_xml() }}
+"""
+
+LIST_HEALTH_CHECKS_REPONSE = """
+
+
+ {% for health_check in health_checks %}
+ {{ health_check.to_xml() }}
+ {% endfor %}
+
+ false
+ {{ health_checks|length }}
+"""
+
+DELETE_HEALTH_CHECK_REPONSE = """
+
+"""
diff --git a/moto/route53/urls.py b/moto/route53/urls.py
index c4031aded..69f776ff0 100644
--- a/moto/route53/urls.py
+++ b/moto/route53/urls.py
@@ -2,11 +2,12 @@ from __future__ import unicode_literals
from . import responses
url_bases = [
- "https://route53.amazonaws.com/201.-..-../hostedzone",
+ "https://route53.amazonaws.com/201.-..-../",
]
url_paths = {
- '{0}$': responses.list_or_create_hostzone_response,
- '{0}/[^/]+$': responses.get_or_delete_hostzone_response,
- '{0}/[^/]+/rrset$': responses.rrset_response,
+ '{0}hostedzone$': responses.list_or_create_hostzone_response,
+ '{0}hostedzone/[^/]+$': responses.get_or_delete_hostzone_response,
+ '{0}hostedzone/[^/]+/rrset$': responses.rrset_response,
+ '{0}healthcheck': responses.health_check_response,
}
diff --git a/tests/test_cloudformation/fixtures/route53_health_check.py b/tests/test_cloudformation/fixtures/route53_health_check.py
new file mode 100644
index 000000000..6c6159fde
--- /dev/null
+++ b/tests/test_cloudformation/fixtures/route53_health_check.py
@@ -0,0 +1,39 @@
+from __future__ import unicode_literals
+
+template = {
+ "Resources" : {
+ "HostedZone": {
+ "Type" : "AWS::Route53::HostedZone",
+ "Properties" : {
+ "Name" : "my_zone"
+ }
+ },
+
+ "my_health_check": {
+ "Type": "AWS::Route53::HealthCheck",
+ "Properties" : {
+ "HealthCheckConfig" : {
+ "FailureThreshold" : 3,
+ "IPAddress" : "10.0.0.4",
+ "Port" : 80,
+ "RequestInterval" : 10,
+ "ResourcePath" : "/",
+ "Type" : "HTTP",
+ }
+ }
+ },
+
+ "myDNSRecord" : {
+ "Type" : "AWS::Route53::RecordSet",
+ "Properties" : {
+ "HostedZoneName" : { "Ref" : "HostedZone" },
+ "Comment" : "DNS name for my instance.",
+ "Name" : "my_record_set",
+ "Type" : "A",
+ "TTL" : "900",
+ "ResourceRecords" : ["my.example.com"],
+ "HealthCheckId": {"Ref": "my_health_check"},
+ }
+ }
+ },
+}
\ No newline at end of file
diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py
index 636cdea24..944457ac4 100644
--- a/tests/test_cloudformation/test_cloudformation_stack_integration.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py
@@ -28,6 +28,7 @@ from .fixtures import (
fn_join,
rds_mysql_with_read_replica,
route53_ec2_instance_with_public_ip,
+ route53_health_check,
route53_roundrobin,
single_instance_with_ebs_volume,
vpc_eip,
@@ -845,3 +846,38 @@ def test_route53_ec2_instance_with_public_ip():
record_set1.ttl.should.equal('900')
record_set1.weight.should.equal(None)
record_set1.resource_records[0].should.equal("10.0.0.25")
+
+
+@mock_cloudformation()
+@mock_route53()
+def test_route53_associate_health_check():
+ route53_conn = boto.connect_route53()
+
+ template_json = json.dumps(route53_health_check.template)
+ conn = boto.cloudformation.connect_to_region("us-west-1")
+ stack = conn.create_stack(
+ "test_stack",
+ template_body=template_json,
+ )
+
+ checks = route53_conn.get_list_health_checks()['ListHealthChecksResponse']['HealthChecks']
+ list(checks).should.have.length_of(1)
+ check = checks[0]
+ health_check_id = check['Id']
+ config = check['HealthCheckConfig']
+ config["FailureThreshold"].should.equal("3")
+ config["IPAddress"].should.equal("10.0.0.4")
+ config["Port"].should.equal("80")
+ config["RequestInterval"].should.equal("10")
+ config["ResourcePath"].should.equal("/")
+ config["Type"].should.equal("HTTP")
+
+ zones = route53_conn.get_all_hosted_zones()['ListHostedZonesResponse']['HostedZones']
+ list(zones).should.have.length_of(1)
+ zone_id = zones[0]['Id']
+
+ rrsets = route53_conn.get_all_rrsets(zone_id)
+ rrsets.should.have.length_of(1)
+
+ record_set = rrsets[0]
+ record_set.health_check.should.equal(health_check_id)
diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py
index fbfd6f2a5..5ba8907ef 100644
--- a/tests/test_route53/test_route53.py
+++ b/tests/test_route53/test_route53.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import boto
+from boto.route53.healthcheck import HealthCheck
from boto.route53.record import ResourceRecordSets
import sure # noqa
@@ -89,3 +90,79 @@ def test_rrset():
rrsets = conn.get_all_rrsets(zoneid, name="foo.foo.testdns.aws.com", type="A")
rrsets.should.have.length_of(0)
+
+
+@mock_route53
+def test_create_health_check():
+ conn = boto.connect_route53('the_key', 'the_secret')
+
+ check = HealthCheck(
+ ip_addr="10.0.0.25",
+ port=80,
+ hc_type="HTTP",
+ resource_path="/",
+ fqdn="example.com",
+ string_match="a good response",
+ request_interval=10,
+ failure_threshold=2,
+ )
+ conn.create_health_check(check)
+
+ checks = conn.get_list_health_checks()['ListHealthChecksResponse']['HealthChecks']
+ list(checks).should.have.length_of(1)
+ check = checks[0]
+ config = check['HealthCheckConfig']
+ config['IPAddress'].should.equal("10.0.0.25")
+ config['Port'].should.equal("80")
+ config['Type'].should.equal("HTTP")
+ config['ResourcePath'].should.equal("/")
+ config['FullyQualifiedDomainName'].should.equal("example.com")
+ config['SearchString'].should.equal("a good response")
+ config['RequestInterval'].should.equal("10")
+ config['FailureThreshold'].should.equal("2")
+
+
+@mock_route53
+def test_delete_health_check():
+ conn = boto.connect_route53('the_key', 'the_secret')
+
+ check = HealthCheck(
+ ip_addr="10.0.0.25",
+ port=80,
+ hc_type="HTTP",
+ resource_path="/",
+ )
+ conn.create_health_check(check)
+
+ checks = conn.get_list_health_checks()['ListHealthChecksResponse']['HealthChecks']
+ list(checks).should.have.length_of(1)
+ health_check_id = checks[0]['Id']
+
+ conn.delete_health_check(health_check_id)
+ checks = conn.get_list_health_checks()['ListHealthChecksResponse']['HealthChecks']
+ list(checks).should.have.length_of(0)
+
+
+@mock_route53
+def test_use_health_check_in_resource_record_set():
+ conn = boto.connect_route53('the_key', 'the_secret')
+
+ check = HealthCheck(
+ ip_addr="10.0.0.25",
+ port=80,
+ hc_type="HTTP",
+ resource_path="/",
+ )
+ check = conn.create_health_check(check)['CreateHealthCheckResponse']['HealthCheck']
+ check_id = check['Id']
+
+ zone = conn.create_hosted_zone("testdns.aws.com")
+ zone_id = zone["CreateHostedZoneResponse"]["HostedZone"]["Id"].split("/")[-1]
+
+ changes = ResourceRecordSets(conn, zone_id)
+ change = changes.add_change("CREATE", "foo.bar.testdns.aws.com", "A", health_check=check_id)
+ change.add_value("1.2.3.4")
+ changes.commit()
+
+ record_sets = conn.get_all_rrsets(zone_id)
+ record_sets[0].health_check.should.equal(check_id)