diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index d2def447b..106d32f4c 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -5776,7 +5776,7 @@ - [X] get_health_check - [ ] get_health_check_count - [ ] get_health_check_last_failure_reason -- [ ] get_health_check_status +- [X] get_health_check_status - [X] get_hosted_zone - [X] get_hosted_zone_count - [ ] get_hosted_zone_limit @@ -7444,4 +7444,4 @@ - workspaces - workspaces-web - xray - \ No newline at end of file + diff --git a/docs/docs/services/route53.rst b/docs/docs/services/route53.rst index 148621fd2..60130848c 100644 --- a/docs/docs/services/route53.rst +++ b/docs/docs/services/route53.rst @@ -65,7 +65,7 @@ route53 - [X] get_health_check - [ ] get_health_check_count - [ ] get_health_check_last_failure_reason -- [ ] get_health_check_status +- [X] get_health_check_status - [X] get_hosted_zone - [X] get_hosted_zone_count - [ ] get_hosted_zone_limit diff --git a/moto/route53/responses.py b/moto/route53/responses.py index ad18f6dbd..647166fd1 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -1,4 +1,6 @@ """Handles Route53 API requests, invokes method and returns response.""" +import datetime +import re from urllib.parse import parse_qs, urlparse from jinja2 import Template @@ -7,6 +9,7 @@ import xmltodict from moto.core.common_types import TYPE_RESPONSE from moto.core.responses import BaseResponse +from moto.core.utils import iso_8601_datetime_with_milliseconds from moto.route53.exceptions import InvalidChangeBatch from moto.route53.models import route53_backends, Route53Backend @@ -342,6 +345,31 @@ class Route53(BaseResponse): template = Template(UPDATE_HEALTH_CHECK_RESPONSE) return 200, headers, template.render(health_check=health_check) + def health_check_status_response(self, request: Any, full_url: str, headers: Any) -> TYPE_RESPONSE: # type: ignore[return] + self.setup_class(request, full_url, headers) + + parsed_url = urlparse(full_url) + method = request.method + + health_check_id = re.search( + r"healthcheck/(?P[^/]+)/status$", parsed_url.path + ).group( # type: ignore[union-attr] + "health_check_id" + ) + + if method == "GET": + self.backend.get_health_check(health_check_id) + template = Template(GET_HEALTH_CHECK_STATUS_RESPONSE) + return ( + 200, + headers, + template.render( + timestamp=iso_8601_datetime_with_milliseconds( + datetime.datetime.utcnow() + ), + ), + ) + def not_implemented_response( self, request: Any, full_url: str, headers: Any ) -> TYPE_RESPONSE: @@ -846,6 +874,21 @@ GET_HEALTH_CHECK_RESPONSE = """ """ +GET_HEALTH_CHECK_STATUS_RESPONSE = """ + + + + 127.0.13.37 + us-east-1 + + {{ timestamp }} + Success: HTTP Status Code: 200. Resolved IP: 127.0.13.37. OK + + + + +""" + UPDATE_HOSTED_ZONE_COMMENT_RESPONSE = """ diff --git a/moto/route53/urls.py b/moto/route53/urls.py index 82a11174b..ba33771f7 100644 --- a/moto/route53/urls.py +++ b/moto/route53/urls.py @@ -32,6 +32,7 @@ url_paths = { r"{0}/(?P[\d_-]+)/hostedzonecount": Route53().get_hosted_zone_count_response, r"{0}/(?P[\d_-]+)/healthcheck$": Route53().health_check_response1, r"{0}/(?P[\d_-]+)/healthcheck/(?P[^/]+)$": Route53().health_check_response2, + r"{0}/(?P[\d_-]+)/healthcheck/(?P[^/]+)/status$": Route53().health_check_status_response, r"{0}/(?P[\d_-]+)/tags/healthcheck/(?P[^/]+)$": tag_response1, r"{0}/(?P[\d_-]+)/tags/hostedzone/(?P[^/]+)$": tag_response2, r"{0}/(?P[\d_-]+)/trafficpolicyinstances/*": Route53().not_implemented_response, diff --git a/tests/test_route53/test_route53_healthchecks.py b/tests/test_route53/test_route53_healthchecks.py index c6e3f6cde..3b8210dee 100644 --- a/tests/test_route53/test_route53_healthchecks.py +++ b/tests/test_route53/test_route53_healthchecks.py @@ -296,3 +296,36 @@ def test_update_health_check(): config.should.have.key("Disabled").equals(False) config.should.have.key("ChildHealthChecks").equals(["child"]) config.should.have.key("Regions").equals(["us-east-1", "us-east-2", "us-west-1"]) + + +@mock_route53 +def test_health_check_status(): + client = boto3.client("route53", region_name="us-east-1") + + hc_id = client.create_health_check( + CallerReference="callref", + HealthCheckConfig={ + "Type": "CALCULATED", + "Inverted": False, + "Disabled": False, + "HealthThreshold": 1, + }, + )["HealthCheck"]["Id"] + + resp = client.get_health_check_status(HealthCheckId=hc_id) + resp["HealthCheckObservations"].should.have.length_of(1) + + observation = resp["HealthCheckObservations"][0] + observation.should.have.key("Region").being.equals("us-east-1") + observation.should.have.key("IPAddress").being.equals("127.0.13.37") + observation.should.have.key("StatusReport") + observation["StatusReport"].should.have.key("Status").being.equals( + "Success: HTTP Status Code: 200. Resolved IP: 127.0.13.37. OK" + ) + + with pytest.raises(ClientError) as exc: + client.get_health_check_status(HealthCheckId="bad-id") + + err = exc.value.response["Error"] + err["Code"].should.equal("NoSuchHealthCheck") + err["Message"].should.equal("A health check with id bad-id does not exist.")