From fdf27a70e93a49b3dabba180e746cb2201403abd Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sun, 22 Aug 2021 10:51:21 +0100 Subject: [PATCH] Route53 - Persist CallerReference (#3788) --- moto/route53/models.py | 59 ++++++-- moto/route53/responses.py | 32 +++-- tests/test_route53/test_route53_boto3.py | 169 +++++++++++++++++++++++ 3 files changed, 234 insertions(+), 26 deletions(-) create mode 100644 tests/test_route53/test_route53_boto3.py diff --git a/moto/route53/models.py b/moto/route53/models.py index be5109f74..96360f4c1 100644 --- a/moto/route53/models.py +++ b/moto/route53/models.py @@ -20,16 +20,23 @@ def create_route53_zone_id(): class HealthCheck(CloudFormationModel): - def __init__(self, health_check_id, health_check_args): + def __init__(self, health_check_id, caller_reference, 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.port = health_check_args.get("port") or 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) + self.request_interval = health_check_args.get("request_interval") or 30 + self.failure_threshold = health_check_args.get("failure_threshold") or 3 + self.health_threshold = health_check_args.get("health_threshold") + self.measure_latency = health_check_args.get("measure_latency") or False + self.inverted = health_check_args.get("inverted") or False + self.disabled = health_check_args.get("disabled") or False + self.enable_sni = health_check_args.get("enable_sni") or False + self.children = health_check_args.get("children") or None + self.caller_reference = caller_reference @property def physical_resource_id(self): @@ -59,25 +66,49 @@ class HealthCheck(CloudFormationModel): "request_interval": properties.get("RequestInterval"), "failure_threshold": properties.get("FailureThreshold"), } - health_check = route53_backend.create_health_check(health_check_args) + health_check = route53_backend.create_health_check( + caller_reference=resource_name, health_check_args=health_check_args + ) return health_check def to_xml(self): template = Template( """ {{ health_check.id }} - example.com 192.0.2.17 + {{ health_check.caller_reference }} - {{ health_check.ip_address }} - {{ health_check.port }} + {% if health_check.type_ != "CALCULATED" %} + {{ health_check.ip_address }} + {{ health_check.port }} + {% endif %} {{ health_check.type_ }} - {{ health_check.resource_path }} - {{ health_check.fqdn }} - {{ health_check.request_interval }} - {{ health_check.failure_threshold }} + {% if health_check.resource_path %} + {{ health_check.resource_path }} + {% endif %} + {% if health_check.fqdn %} + {{ health_check.fqdn }} + {% endif %} + {% if health_check.type_ != "CALCULATED" %} + {{ health_check.request_interval }} + {{ health_check.failure_threshold }} + {{ health_check.measure_latency }} + {% endif %} + {% if health_check.type_ == "CALCULATED" %} + {{ health_check.health_threshold }} + {% endif %} + {{ health_check.inverted }} + {{ health_check.disabled }} + {{ health_check.enable_sni }} {% if health_check.search_string %} {{ health_check.search_string }} {% endif %} + {% if health_check.children %} + + {% for child in health_check.children %} + {{ child }} + {% endfor %} + + {% endif %} 1 """ @@ -390,9 +421,9 @@ class Route53Backend(BaseBackend): def delete_hosted_zone(self, id_): return self.zones.pop(id_.replace("/hostedzone/", ""), None) - def create_health_check(self, health_check_args): + def create_health_check(self, caller_reference, health_check_args): health_check_id = str(uuid.uuid4()) - health_check = HealthCheck(health_check_id, health_check_args) + health_check = HealthCheck(health_check_id, caller_reference, health_check_args) self.health_checks[health_check_id] = health_check return health_check diff --git a/moto/route53/responses.py b/moto/route53/responses.py index e6be50c0e..58f67dea3 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -182,20 +182,28 @@ class Route53(BaseResponse): method = request.method if method == "POST": - properties = xmltodict.parse(self.body)["CreateHealthCheckRequest"][ - "HealthCheckConfig" - ] + json_body = xmltodict.parse(self.body)["CreateHealthCheckRequest"] + caller_reference = json_body["CallerReference"] + config = json_body["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"), + "ip_address": config.get("IPAddress"), + "port": config.get("Port"), + "type": config["Type"], + "resource_path": config.get("ResourcePath"), + "fqdn": config.get("FullyQualifiedDomainName"), + "search_string": config.get("SearchString"), + "request_interval": config.get("RequestInterval"), + "failure_threshold": config.get("FailureThreshold"), + "health_threshold": config.get("HealthThreshold"), + "measure_latency": config.get("MeasureLatency"), + "inverted": config.get("Inverted"), + "disabled": config.get("Disabled"), + "enable_sni": config.get("EnableSNI"), + "children": config.get("ChildHealthChecks", {}).get("ChildHealthCheck"), } - health_check = route53_backend.create_health_check(health_check_args) + health_check = route53_backend.create_health_check( + caller_reference, health_check_args + ) template = Template(CREATE_HEALTH_CHECK_RESPONSE) return 201, headers, template.render(health_check=health_check) elif method == "DELETE": diff --git a/tests/test_route53/test_route53_boto3.py b/tests/test_route53/test_route53_boto3.py new file mode 100644 index 000000000..3ed9e9718 --- /dev/null +++ b/tests/test_route53/test_route53_boto3.py @@ -0,0 +1,169 @@ +import boto3 +import sure # noqa +from moto import mock_route53 + + +@mock_route53 +def test_create_health_check(): + client = boto3.client("route53", region_name="us-east-1") + + response = client.create_health_check( + CallerReference="test-route53-health-HealthCheck-asdf", + HealthCheckConfig={ + "IPAddress": "93.184.216.34", + "Port": 80, + "Type": "HTTP", + "ResourcePath": "/", + "FullyQualifiedDomainName": "example.com", + "RequestInterval": 10, + "FailureThreshold": 2, + }, + ) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(201) + # + check = response["HealthCheck"] + check.should.have.key("Id") + check.should.have.key("CallerReference").being.equal( + "test-route53-health-HealthCheck-asdf" + ) + check.should.have.key("HealthCheckConfig") + # + config = check["HealthCheckConfig"] + config.should.have.key("IPAddress").being.equal("93.184.216.34") + config.should.have.key("Port").being.equal(80) + config.should.have.key("Type").being.equal("HTTP") + config.should.have.key("ResourcePath").being.equal("/") + config.should.have.key("FullyQualifiedDomainName").being.equal("example.com") + config.should.have.key("RequestInterval").being.equal(10) + config.should.have.key("FailureThreshold").being.equal(2) + config.should.have.key("MeasureLatency").being.equal(False) + config.should.have.key("Inverted").being.equal(False) + config.should.have.key("Disabled").being.equal(False) + config.should.have.key("EnableSNI").being.equal(False) + + config.shouldnt.have.key("ChildHealthChecks") + config.shouldnt.have.key("HealthThreshold") + + +@mock_route53 +def test_create_health_check_with_additional_options(): + client = boto3.client("route53", region_name="us-east-1") + + response = client.create_health_check( + CallerReference="test-route53-health-HealthCheck-asdf", + HealthCheckConfig={ + "IPAddress": "93.184.216.34", + "Port": 80, + "Type": "HTTP", + "ResourcePath": "/", + "FullyQualifiedDomainName": "example.com", + "RequestInterval": 10, + "FailureThreshold": 2, + "MeasureLatency": True, + "Inverted": True, + "Disabled": True, + "EnableSNI": True, + }, + ) + response["ResponseMetadata"]["HTTPStatusCode"].should.equal(201) + # + check = response["HealthCheck"] + check.should.have.key("CallerReference").being.equal( + "test-route53-health-HealthCheck-asdf" + ) + check.should.have.key("HealthCheckConfig") + # + config = check["HealthCheckConfig"] + config.should.have.key("MeasureLatency").being.equal(True) + config.should.have.key("Inverted").being.equal(True) + config.should.have.key("Disabled").being.equal(True) + config.should.have.key("EnableSNI").being.equal(True) + + +@mock_route53 +def test_create_calculated_health_check(): + client = boto3.client("route53", region_name="us-east-1") + + response = client.create_health_check( + CallerReference="test-route53-health-HealthCheck-ZHV123", + HealthCheckConfig={ + "Type": "CALCULATED", + "Inverted": False, + "Disabled": False, + "HealthThreshold": 1, + }, + ) + + check = response["HealthCheck"] + check.should.have.key("Id") + check.should.have.key("CallerReference").being.equal( + "test-route53-health-HealthCheck-ZHV123" + ) + # + config = check["HealthCheckConfig"] + config.should.have.key("Type").being.equal("CALCULATED") + config.should.have.key("Inverted").being.equal(False) + config.should.have.key("Disabled").being.equal(False) + config.should.have.key("HealthThreshold").being.equal(1) + # + config.shouldnt.have.key("IPAddress") + config.shouldnt.have.key("Port") + config.shouldnt.have.key("ResourcePath") + config.shouldnt.have.key("FullyQualifiedDomainName") + config.shouldnt.have.key("RequestInterval") + config.shouldnt.have.key("FailureThreshold") + config.shouldnt.have.key("MeasureLatency") + + +@mock_route53 +def test_create_calculated_health_check_with_children(): + client = boto3.client("route53", region_name="us-east-1") + + child1 = client.create_health_check( + CallerReference="test-route53-health-HealthCheck-child1", + HealthCheckConfig={ + "Type": "CALCULATED", + "Inverted": False, + "Disabled": False, + "HealthThreshold": 1, + }, + ) + + child2 = client.create_health_check( + CallerReference="test-route53-health-HealthCheck-child2", + HealthCheckConfig={ + "Type": "CALCULATED", + "Inverted": False, + "Disabled": False, + "HealthThreshold": 1, + }, + ) + + parent = client.create_health_check( + CallerReference="test-route53-health-HealthCheck-parent", + HealthCheckConfig={ + "Type": "CALCULATED", + "Inverted": False, + "Disabled": False, + "HealthThreshold": 1, + "ChildHealthChecks": [ + child1["HealthCheck"]["Id"], + child2["HealthCheck"]["Id"], + ], + }, + ) + + check = parent["HealthCheck"] + check.should.have.key("Id") + check.should.have.key("CallerReference").being.equal( + "test-route53-health-HealthCheck-parent" + ) + # + config = check["HealthCheckConfig"] + config.should.have.key("Type").being.equal("CALCULATED") + config.should.have.key("Inverted").being.equal(False) + config.should.have.key("Disabled").being.equal(False) + config.should.have.key("HealthThreshold").being.equal(1) + config.should.have.key("ChildHealthChecks").being.equal( + [child1["HealthCheck"]["Id"], child2["HealthCheck"]["Id"]] + )