Skip to content

Commit

Permalink
Route53 - Error on batch changes that are too large (#4674)
Browse files Browse the repository at this point in the history
  • Loading branch information
bblommers committed Dec 10, 2021
1 parent 7776668 commit 3d10536
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 1 deletion.
9 changes: 9 additions & 0 deletions moto/route53/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,12 @@ class QueryLoggingConfigAlreadyExists(Route53ClientError):
def __init__(self):
message = "A query logging configuration already exists for this hosted zone"
super().__init__("QueryLoggingConfigAlreadyExists", message)


class InvalidChangeBatch(Route53ClientError):

code = 400

def __init__(self):
message = "Number of records limit of 1000 exceeded."
super().__init__("InvalidChangeBatch", message)
19 changes: 18 additions & 1 deletion moto/route53/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import xmltodict

from moto.core.responses import BaseResponse
from moto.route53.exceptions import Route53ClientError
from moto.route53.exceptions import Route53ClientError, InvalidChangeBatch
from moto.route53.models import route53_backend

XMLNS = "https://route53.amazonaws.com/doc/2013-04-01/"
Expand Down Expand Up @@ -112,6 +112,23 @@ def rrset_response(self, request, full_url, headers):
]["Change"]
]

# Enforce quotas https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets
# - A request cannot contain more than 1,000 ResourceRecord elements. When the value of the Action element is UPSERT, each ResourceRecord element is counted twice.
effective_rr_count = 0
for value in change_list:
record_set = value["ResourceRecordSet"]
if (
"ResourceRecords" not in record_set
or not record_set["ResourceRecords"]
):
continue
resource_records = list(record_set["ResourceRecords"].values())[0]
effective_rr_count += len(resource_records)
if value["Action"] == "UPSERT":
effective_rr_count += len(resource_records)
if effective_rr_count > 1000:
raise InvalidChangeBatch

error_msg = route53_backend.change_resource_record_sets(zoneid, change_list)
if error_msg:
return 400, headers, error_msg
Expand Down
121 changes: 121 additions & 0 deletions tests/test_route53/test_route53.py
Original file line number Diff line number Diff line change
Expand Up @@ -1472,3 +1472,124 @@ def test_get_change():

response["ChangeInfo"]["Id"].should.equal(change_id)
response["ChangeInfo"]["Status"].should.equal("INSYNC")


@mock_route53
def test_change_resource_record_sets_records_limit():
conn = boto3.client("route53", region_name="us-east-1")
conn.create_hosted_zone(
Name="db.",
CallerReference=str(hash("foo")),
HostedZoneConfig=dict(PrivateZone=True, Comment="db"),
)

zones = conn.list_hosted_zones_by_name(DNSName="db.")
len(zones["HostedZones"]).should.equal(1)
zones["HostedZones"][0]["Name"].should.equal("db.")
hosted_zone_id = zones["HostedZones"][0]["Id"]

# Changes creating exactly 1,000 resource records.
changes = []
for ci in range(4):
resourcerecords = []
for rri in range(250):
resourcerecords.append({"Value": "127.0.0.%d" % (rri)})
changes.append(
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "foo%d.db." % (ci),
"Type": "A",
"TTL": 10,
"ResourceRecords": resourcerecords,
},
}
)
create_1000_resource_records_payload = {
"Comment": "Create four records with 250 resource records each",
"Changes": changes,
}

conn.change_resource_record_sets(
HostedZoneId=hosted_zone_id, ChangeBatch=create_1000_resource_records_payload
)

# Changes creating over 1,000 resource records.
too_many_changes = create_1000_resource_records_payload["Changes"].copy()
too_many_changes.append(
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "toomany.db.",
"Type": "A",
"TTL": 10,
"ResourceRecords": [{"Value": "127.0.0.1"}],
},
}
)

create_1001_resource_records_payload = {
"Comment": "Create four records with 250 resource records each, plus one more",
"Changes": too_many_changes,
}

with pytest.raises(ClientError) as exc:
conn.change_resource_record_sets(
HostedZoneId=hosted_zone_id,
ChangeBatch=create_1001_resource_records_payload,
)
err = exc.value.response["Error"]
err["Code"].should.equal("InvalidChangeBatch")

# Changes upserting exactly 500 resource records.
changes = []
for ci in range(2):
resourcerecords = []
for rri in range(250):
resourcerecords.append({"Value": "127.0.0.%d" % (rri)})
changes.append(
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "foo%d.db." % (ci),
"Type": "A",
"TTL": 10,
"ResourceRecords": resourcerecords,
},
}
)
upsert_500_resource_records_payload = {
"Comment": "Upsert two records with 250 resource records each",
"Changes": changes,
}

conn.change_resource_record_sets(
HostedZoneId=hosted_zone_id, ChangeBatch=upsert_500_resource_records_payload
)

# Changes upserting over 1,000 resource records.
too_many_changes = upsert_500_resource_records_payload["Changes"].copy()
too_many_changes.append(
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "toomany.db.",
"Type": "A",
"TTL": 10,
"ResourceRecords": [{"Value": "127.0.0.1"}],
},
}
)

upsert_501_resource_records_payload = {
"Comment": "Upsert two records with 250 resource records each, plus one more",
"Changes": too_many_changes,
}

with pytest.raises(ClientError) as exc:
conn.change_resource_record_sets(
HostedZoneId=hosted_zone_id, ChangeBatch=upsert_501_resource_records_payload
)
err = exc.value.response["Error"]
err["Code"].should.equal("InvalidChangeBatch")
err["Message"].should.equal("Number of records limit of 1000 exceeded.")

0 comments on commit 3d10536

Please sign in to comment.