Route53 query-logging-config APIs (#4437)
This commit is contained in:
parent
e2f7a1500d
commit
665c8aa3bc
@ -3506,7 +3506,7 @@
|
|||||||
- [X] create_health_check
|
- [X] create_health_check
|
||||||
- [X] create_hosted_zone
|
- [X] create_hosted_zone
|
||||||
- [ ] create_key_signing_key
|
- [ ] create_key_signing_key
|
||||||
- [ ] create_query_logging_config
|
- [X] create_query_logging_config
|
||||||
- [ ] create_reusable_delegation_set
|
- [ ] create_reusable_delegation_set
|
||||||
- [ ] create_traffic_policy
|
- [ ] create_traffic_policy
|
||||||
- [ ] create_traffic_policy_instance
|
- [ ] create_traffic_policy_instance
|
||||||
@ -3516,7 +3516,7 @@
|
|||||||
- [X] delete_health_check
|
- [X] delete_health_check
|
||||||
- [X] delete_hosted_zone
|
- [X] delete_hosted_zone
|
||||||
- [ ] delete_key_signing_key
|
- [ ] delete_key_signing_key
|
||||||
- [ ] delete_query_logging_config
|
- [X] delete_query_logging_config
|
||||||
- [ ] delete_reusable_delegation_set
|
- [ ] delete_reusable_delegation_set
|
||||||
- [ ] delete_traffic_policy
|
- [ ] delete_traffic_policy
|
||||||
- [ ] delete_traffic_policy_instance
|
- [ ] delete_traffic_policy_instance
|
||||||
@ -3536,7 +3536,7 @@
|
|||||||
- [X] get_hosted_zone
|
- [X] get_hosted_zone
|
||||||
- [ ] get_hosted_zone_count
|
- [ ] get_hosted_zone_count
|
||||||
- [ ] get_hosted_zone_limit
|
- [ ] get_hosted_zone_limit
|
||||||
- [ ] get_query_logging_config
|
- [X] get_query_logging_config
|
||||||
- [ ] get_reusable_delegation_set
|
- [ ] get_reusable_delegation_set
|
||||||
- [ ] get_reusable_delegation_set_limit
|
- [ ] get_reusable_delegation_set_limit
|
||||||
- [ ] get_traffic_policy
|
- [ ] get_traffic_policy
|
||||||
@ -3547,7 +3547,7 @@
|
|||||||
- [X] list_hosted_zones
|
- [X] list_hosted_zones
|
||||||
- [X] list_hosted_zones_by_name
|
- [X] list_hosted_zones_by_name
|
||||||
- [ ] list_hosted_zones_by_vpc
|
- [ ] list_hosted_zones_by_vpc
|
||||||
- [ ] list_query_logging_configs
|
- [X] list_query_logging_configs
|
||||||
- [ ] list_resource_record_sets
|
- [ ] list_resource_record_sets
|
||||||
- [ ] list_reusable_delegation_sets
|
- [ ] list_reusable_delegation_sets
|
||||||
- [X] list_tags_for_resource
|
- [X] list_tags_for_resource
|
||||||
|
@ -652,7 +652,9 @@ class LogsBackend(BaseBackend):
|
|||||||
del self.groups[log_group_name]
|
del self.groups[log_group_name]
|
||||||
|
|
||||||
@paginate(pagination_model=PAGINATION_MODEL)
|
@paginate(pagination_model=PAGINATION_MODEL)
|
||||||
def describe_log_groups(self, log_group_name_prefix, limit=None, next_token=None):
|
def describe_log_groups(
|
||||||
|
self, log_group_name_prefix=None, limit=None, next_token=None
|
||||||
|
):
|
||||||
if log_group_name_prefix is None:
|
if log_group_name_prefix is None:
|
||||||
log_group_name_prefix = ""
|
log_group_name_prefix = ""
|
||||||
|
|
||||||
|
73
moto/route53/exceptions.py
Normal file
73
moto/route53/exceptions.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
"""Exceptions raised by the Route53 service."""
|
||||||
|
from moto.core.exceptions import RESTError
|
||||||
|
|
||||||
|
|
||||||
|
class Route53ClientError(RESTError):
|
||||||
|
"""Base class for Route53 errors."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs.setdefault("template", "single_error")
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInput(Route53ClientError):
|
||||||
|
"""Malformed ARN for the CloudWatch log group."""
|
||||||
|
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
message = "The ARN for the CloudWatch Logs log group is invalid"
|
||||||
|
super().__init__("InvalidInput", message)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPaginationToken(Route53ClientError):
|
||||||
|
"""Bad NextToken specified when listing query logging configs."""
|
||||||
|
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
message = (
|
||||||
|
"Route 53 can't get the next page of query logging configurations "
|
||||||
|
"because the specified value for NextToken is invalid."
|
||||||
|
)
|
||||||
|
super().__init__("InvalidPaginationToken", message)
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchCloudWatchLogsLogGroup(Route53ClientError):
|
||||||
|
"""CloudWatch LogGroup has a permissions policy, but does not exist."""
|
||||||
|
|
||||||
|
code = 404
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
message = "The specified CloudWatch Logs log group doesn't exist."
|
||||||
|
super().__init__("NoSuchCloudWatchLogsLogGroup", message)
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchHostedZone(Route53ClientError):
|
||||||
|
"""HostedZone does not exist."""
|
||||||
|
|
||||||
|
code = 404
|
||||||
|
|
||||||
|
def __init__(self, host_zone_id):
|
||||||
|
message = f"No hosted zone found with ID: {host_zone_id}"
|
||||||
|
super().__init__("NoSuchHostedZone", message)
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchQueryLoggingConfig(Route53ClientError):
|
||||||
|
"""Query log config does not exist."""
|
||||||
|
|
||||||
|
code = 404
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
message = "The query logging configuration does not exist"
|
||||||
|
super().__init__("NoSuchQueryLoggingConfig", message)
|
||||||
|
|
||||||
|
|
||||||
|
class QueryLoggingConfigAlreadyExists(Route53ClientError):
|
||||||
|
"""Query log config exists for the hosted zone."""
|
||||||
|
|
||||||
|
code = 409
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
message = "A query logging configuration already exists for this hosted zone"
|
||||||
|
super().__init__("QueryLoggingConfigAlreadyExists", message)
|
@ -1,13 +1,23 @@
|
|||||||
|
"""Route53Backend class with methods for supported APIs."""
|
||||||
import itertools
|
import itertools
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
import re
|
||||||
|
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
from moto.core import BaseBackend, CloudFormationModel
|
from moto.route53.exceptions import (
|
||||||
|
InvalidInput,
|
||||||
|
NoSuchCloudWatchLogsLogGroup,
|
||||||
|
NoSuchHostedZone,
|
||||||
|
NoSuchQueryLoggingConfig,
|
||||||
|
QueryLoggingConfigAlreadyExists,
|
||||||
|
)
|
||||||
|
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
||||||
|
from moto.utilities.paginator import paginate
|
||||||
|
from .utils import PAGINATION_MODEL
|
||||||
|
|
||||||
ROUTE53_ID_CHOICE = string.ascii_uppercase + string.digits
|
ROUTE53_ID_CHOICE = string.ascii_uppercase + string.digits
|
||||||
|
|
||||||
@ -342,7 +352,7 @@ class RecordSetGroup(CloudFormationModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self):
|
||||||
return "arn:aws:route53:::hostedzone/{0}".format(self.hosted_zone_id)
|
return f"arn:aws:route53:::hostedzone/{self.hosted_zone_id}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_name_type():
|
def cloudformation_name_type():
|
||||||
@ -372,11 +382,37 @@ class RecordSetGroup(CloudFormationModel):
|
|||||||
return record_set_group
|
return record_set_group
|
||||||
|
|
||||||
|
|
||||||
|
class QueryLoggingConfig(BaseModel):
|
||||||
|
|
||||||
|
"""QueryLoggingConfig class; this object isn't part of Cloudformation."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, query_logging_config_id, hosted_zone_id, cloudwatch_logs_log_group_arn
|
||||||
|
):
|
||||||
|
self.hosted_zone_id = hosted_zone_id
|
||||||
|
self.cloudwatch_logs_log_group_arn = cloudwatch_logs_log_group_arn
|
||||||
|
self.query_logging_config_id = query_logging_config_id
|
||||||
|
self.location = f"https://route53.amazonaws.com/2013-04-01/queryloggingconfig/{self.query_logging_config_id}"
|
||||||
|
|
||||||
|
def to_xml(self):
|
||||||
|
template = Template(
|
||||||
|
"""<QueryLoggingConfig>
|
||||||
|
<CloudWatchLogsLogGroupArn>{{ query_logging_config.cloudwatch_logs_log_group_arn }}</CloudWatchLogsLogGroupArn>
|
||||||
|
<HostedZoneId>{{ query_logging_config.hosted_zone_id }}</HostedZoneId>
|
||||||
|
<Id>{{ query_logging_config.query_logging_config_id }}</Id>
|
||||||
|
</QueryLoggingConfig>"""
|
||||||
|
)
|
||||||
|
# The "Location" value must be put into the header; that's done in
|
||||||
|
# responses.py.
|
||||||
|
return template.render(query_logging_config=self)
|
||||||
|
|
||||||
|
|
||||||
class Route53Backend(BaseBackend):
|
class Route53Backend(BaseBackend):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.zones = {}
|
self.zones = {}
|
||||||
self.health_checks = {}
|
self.health_checks = {}
|
||||||
self.resource_tags = defaultdict(dict)
|
self.resource_tags = defaultdict(dict)
|
||||||
|
self.query_logging_configs = {}
|
||||||
|
|
||||||
def create_hosted_zone(self, name, private_zone, comment=None):
|
def create_hosted_zone(self, name, private_zone, comment=None):
|
||||||
new_id = create_route53_zone_id()
|
new_id = create_route53_zone_id()
|
||||||
@ -414,13 +450,10 @@ class Route53Backend(BaseBackend):
|
|||||||
cleaned_hosted_zone_name = the_zone.name.strip(".")
|
cleaned_hosted_zone_name = the_zone.name.strip(".")
|
||||||
|
|
||||||
if not cleaned_record_name.endswith(cleaned_hosted_zone_name):
|
if not cleaned_record_name.endswith(cleaned_hosted_zone_name):
|
||||||
error_msg = """
|
error_msg = f"""
|
||||||
An error occurred (InvalidChangeBatch) when calling the ChangeResourceRecordSets operation:
|
An error occurred (InvalidChangeBatch) when calling the ChangeResourceRecordSets operation:
|
||||||
RRSet with DNS name %s is not permitted in zone %s
|
RRSet with DNS name {record_set["Name"]} is not permitted in zone {the_zone.name}
|
||||||
""" % (
|
"""
|
||||||
record_set["Name"],
|
|
||||||
the_zone.name,
|
|
||||||
)
|
|
||||||
return error_msg
|
return error_msg
|
||||||
|
|
||||||
if not record_set["Name"].endswith("."):
|
if not record_set["Name"].endswith("."):
|
||||||
@ -477,6 +510,7 @@ class Route53Backend(BaseBackend):
|
|||||||
for zone in self.list_hosted_zones():
|
for zone in self.list_hosted_zones():
|
||||||
if zone.name == name:
|
if zone.name == name:
|
||||||
return zone
|
return zone
|
||||||
|
return None
|
||||||
|
|
||||||
def delete_hosted_zone(self, id_):
|
def delete_hosted_zone(self, id_):
|
||||||
return self.zones.pop(id_.replace("/hostedzone/", ""), None)
|
return self.zones.pop(id_.replace("/hostedzone/", ""), None)
|
||||||
@ -493,5 +527,92 @@ class Route53Backend(BaseBackend):
|
|||||||
def delete_health_check(self, health_check_id):
|
def delete_health_check(self, health_check_id):
|
||||||
return self.health_checks.pop(health_check_id, None)
|
return self.health_checks.pop(health_check_id, None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_arn(region, arn):
|
||||||
|
match = re.match(fr"arn:aws:logs:{region}:\d{{12}}:log-group:.+", arn)
|
||||||
|
if not arn or not match:
|
||||||
|
raise InvalidInput()
|
||||||
|
|
||||||
|
# The CloudWatch Logs log group must be in the "us-east-1" region.
|
||||||
|
match = re.match(r"^(?:[^:]+:){3}(?P<region>[^:]+).*", arn)
|
||||||
|
if match.group("region") != "us-east-1":
|
||||||
|
raise InvalidInput()
|
||||||
|
|
||||||
|
def create_query_logging_config(self, region, hosted_zone_id, log_group_arn):
|
||||||
|
"""Process the create_query_logging_config request."""
|
||||||
|
# Does the hosted_zone_id exist?
|
||||||
|
response = self.list_hosted_zones()
|
||||||
|
zones = list(response) if response else []
|
||||||
|
for zone in zones:
|
||||||
|
if zone.id == hosted_zone_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise NoSuchHostedZone(hosted_zone_id)
|
||||||
|
|
||||||
|
# Ensure CloudWatch Logs log ARN is valid, otherwise raise an error.
|
||||||
|
self._validate_arn(region, log_group_arn)
|
||||||
|
|
||||||
|
# Note: boto3 checks the resource policy permissions before checking
|
||||||
|
# whether the log group exists. moto doesn't have a way of checking
|
||||||
|
# the resource policy, so in some instances moto will complain
|
||||||
|
# about a log group that doesn't exist whereas boto3 will complain
|
||||||
|
# that "The resource policy that you're using for Route 53 query
|
||||||
|
# logging doesn't grant Route 53 sufficient permission to create
|
||||||
|
# a log stream in the specified log group."
|
||||||
|
|
||||||
|
from moto.logs import logs_backends # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
response = logs_backends[region].describe_log_groups()
|
||||||
|
log_groups = response[0] if response else []
|
||||||
|
for entry in log_groups:
|
||||||
|
if log_group_arn == entry["arn"]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# There is no CloudWatch Logs log group with the specified ARN.
|
||||||
|
raise NoSuchCloudWatchLogsLogGroup()
|
||||||
|
|
||||||
|
# Verify there is no existing query log config using the same hosted
|
||||||
|
# zone.
|
||||||
|
for query_log in self.query_logging_configs.values():
|
||||||
|
if query_log.hosted_zone_id == hosted_zone_id:
|
||||||
|
raise QueryLoggingConfigAlreadyExists()
|
||||||
|
|
||||||
|
# Create an instance of the query logging config.
|
||||||
|
query_logging_config_id = str(uuid.uuid4())
|
||||||
|
query_logging_config = QueryLoggingConfig(
|
||||||
|
query_logging_config_id, hosted_zone_id, log_group_arn
|
||||||
|
)
|
||||||
|
self.query_logging_configs[query_logging_config_id] = query_logging_config
|
||||||
|
return query_logging_config
|
||||||
|
|
||||||
|
def delete_query_logging_config(self, query_logging_config_id):
|
||||||
|
"""Delete query logging config, if it exists."""
|
||||||
|
if query_logging_config_id not in self.query_logging_configs:
|
||||||
|
raise NoSuchQueryLoggingConfig()
|
||||||
|
self.query_logging_configs.pop(query_logging_config_id)
|
||||||
|
|
||||||
|
def get_query_logging_config(self, query_logging_config_id):
|
||||||
|
"""Return query logging config, if it exists."""
|
||||||
|
if query_logging_config_id not in self.query_logging_configs:
|
||||||
|
raise NoSuchQueryLoggingConfig()
|
||||||
|
return self.query_logging_configs[query_logging_config_id]
|
||||||
|
|
||||||
|
@paginate(pagination_model=PAGINATION_MODEL)
|
||||||
|
def list_query_logging_configs(
|
||||||
|
self, hosted_zone_id=None, next_token=None, max_results=None,
|
||||||
|
): # pylint: disable=unused-argument
|
||||||
|
"""Return a list of query logging configs."""
|
||||||
|
if hosted_zone_id:
|
||||||
|
# Does the hosted_zone_id exist?
|
||||||
|
response = self.list_hosted_zones()
|
||||||
|
zones = list(response) if response else []
|
||||||
|
for zone in zones:
|
||||||
|
if zone.id == hosted_zone_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise NoSuchHostedZone(hosted_zone_id)
|
||||||
|
|
||||||
|
return list(self.query_logging_configs.values())
|
||||||
|
|
||||||
|
|
||||||
route53_backend = Route53Backend()
|
route53_backend = Route53Backend()
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
from jinja2 import Template
|
"""Handles Route53 API requests, invokes method and returns response."""
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from jinja2 import Template
|
||||||
from .models import route53_backend
|
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
|
||||||
|
from moto.core.responses import BaseResponse
|
||||||
|
from moto.core.exceptions import InvalidToken
|
||||||
|
from moto.route53.exceptions import Route53ClientError, InvalidPaginationToken
|
||||||
|
from moto.route53.models import route53_backend
|
||||||
|
|
||||||
XMLNS = "https://route53.amazonaws.com/doc/2013-04-01/"
|
XMLNS = "https://route53.amazonaws.com/doc/2013-04-01/"
|
||||||
|
|
||||||
|
|
||||||
class Route53(BaseResponse):
|
class Route53(BaseResponse):
|
||||||
|
"""Handler for Route53 requests and responses."""
|
||||||
|
|
||||||
def list_or_create_hostzone_response(self, request, full_url, headers):
|
def list_or_create_hostzone_response(self, request, full_url, headers):
|
||||||
self.setup_class(request, full_url, headers)
|
self.setup_class(request, full_url, headers)
|
||||||
|
|
||||||
@ -56,7 +62,7 @@ class Route53(BaseResponse):
|
|||||||
dnsname, zones = route53_backend.list_hosted_zones_by_name(dnsname)
|
dnsname, zones = route53_backend.list_hosted_zones_by_name(dnsname)
|
||||||
|
|
||||||
template = Template(LIST_HOSTED_ZONES_BY_NAME_RESPONSE)
|
template = Template(LIST_HOSTED_ZONES_BY_NAME_RESPONSE)
|
||||||
return 200, headers, template.render(zones=zones, dnsname=dnsname)
|
return 200, headers, template.render(zones=zones, dnsname=dnsname, xmlns=XMLNS)
|
||||||
|
|
||||||
def get_or_delete_hostzone_response(self, request, full_url, headers):
|
def get_or_delete_hostzone_response(self, request, full_url, headers):
|
||||||
self.setup_class(request, full_url, headers)
|
self.setup_class(request, full_url, headers)
|
||||||
@ -148,15 +154,20 @@ class Route53(BaseResponse):
|
|||||||
caller_reference, health_check_args
|
caller_reference, health_check_args
|
||||||
)
|
)
|
||||||
template = Template(CREATE_HEALTH_CHECK_RESPONSE)
|
template = Template(CREATE_HEALTH_CHECK_RESPONSE)
|
||||||
return 201, headers, template.render(health_check=health_check)
|
return 201, headers, template.render(health_check=health_check, xmlns=XMLNS)
|
||||||
elif method == "DELETE":
|
elif method == "DELETE":
|
||||||
health_check_id = parsed_url.path.split("/")[-1]
|
health_check_id = parsed_url.path.split("/")[-1]
|
||||||
route53_backend.delete_health_check(health_check_id)
|
route53_backend.delete_health_check(health_check_id)
|
||||||
return 200, headers, DELETE_HEALTH_CHECK_RESPONSE
|
template = Template(DELETE_HEALTH_CHECK_RESPONSE)
|
||||||
|
return 200, headers, template.render(xmlns=XMLNS)
|
||||||
elif method == "GET":
|
elif method == "GET":
|
||||||
template = Template(LIST_HEALTH_CHECKS_RESPONSE)
|
template = Template(LIST_HEALTH_CHECKS_RESPONSE)
|
||||||
health_checks = route53_backend.list_health_checks()
|
health_checks = route53_backend.list_health_checks()
|
||||||
return 200, headers, template.render(health_checks=health_checks)
|
return (
|
||||||
|
200,
|
||||||
|
headers,
|
||||||
|
template.render(health_checks=health_checks, xmlns=XMLNS),
|
||||||
|
)
|
||||||
|
|
||||||
def not_implemented_response(self, request, full_url, headers):
|
def not_implemented_response(self, request, full_url, headers):
|
||||||
self.setup_class(request, full_url, headers)
|
self.setup_class(request, full_url, headers)
|
||||||
@ -167,7 +178,7 @@ class Route53(BaseResponse):
|
|||||||
elif "trafficpolicyinstances" in full_url:
|
elif "trafficpolicyinstances" in full_url:
|
||||||
action = "policies"
|
action = "policies"
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"The action for {0} has not been implemented for route 53".format(action)
|
f"The action for {action} has not been implemented for route 53"
|
||||||
)
|
)
|
||||||
|
|
||||||
def list_or_change_tags_for_resource_request(self, request, full_url, headers):
|
def list_or_change_tags_for_resource_request(self, request, full_url, headers):
|
||||||
@ -196,7 +207,6 @@ class Route53(BaseResponse):
|
|||||||
|
|
||||||
route53_backend.change_tags_for_resource(id_, tags)
|
route53_backend.change_tags_for_resource(id_, tags)
|
||||||
template = Template(CHANGE_TAGS_FOR_RESOURCE_RESPONSE)
|
template = Template(CHANGE_TAGS_FOR_RESOURCE_RESPONSE)
|
||||||
|
|
||||||
return 200, headers, template.render()
|
return 200, headers, template.render()
|
||||||
|
|
||||||
def get_change(self, request, full_url, headers):
|
def get_change(self, request, full_url, headers):
|
||||||
@ -206,20 +216,100 @@ class Route53(BaseResponse):
|
|||||||
parsed_url = urlparse(full_url)
|
parsed_url = urlparse(full_url)
|
||||||
change_id = parsed_url.path.rstrip("/").rsplit("/", 1)[1]
|
change_id = parsed_url.path.rstrip("/").rsplit("/", 1)[1]
|
||||||
template = Template(GET_CHANGE_RESPONSE)
|
template = Template(GET_CHANGE_RESPONSE)
|
||||||
return 200, headers, template.render(change_id=change_id)
|
return 200, headers, template.render(change_id=change_id, xmlns=XMLNS)
|
||||||
|
|
||||||
|
def list_or_create_query_logging_config_response(self, request, full_url, headers):
|
||||||
|
self.setup_class(request, full_url, headers)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
json_body = xmltodict.parse(self.body)["CreateQueryLoggingConfigRequest"]
|
||||||
|
hosted_zone_id = json_body["HostedZoneId"]
|
||||||
|
log_group_arn = json_body["CloudWatchLogsLogGroupArn"]
|
||||||
|
try:
|
||||||
|
query_logging_config = route53_backend.create_query_logging_config(
|
||||||
|
self.region, hosted_zone_id, log_group_arn
|
||||||
|
)
|
||||||
|
except Route53ClientError as r53error:
|
||||||
|
return r53error.code, {}, r53error.description
|
||||||
|
|
||||||
|
template = Template(CREATE_QUERY_LOGGING_CONFIG_RESPONSE)
|
||||||
|
headers["Location"] = query_logging_config.location
|
||||||
|
return (
|
||||||
|
201,
|
||||||
|
headers,
|
||||||
|
template.render(query_logging_config=query_logging_config, xmlns=XMLNS),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif request.method == "GET":
|
||||||
|
hosted_zone_id = self._get_param("hostedzoneid")
|
||||||
|
next_token = self._get_param("nexttoken")
|
||||||
|
max_results = self._get_int_param("maxresults")
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
# The paginator picks up named arguments, returns tuple.
|
||||||
|
# pylint: disable=unbalanced-tuple-unpacking
|
||||||
|
(
|
||||||
|
all_configs,
|
||||||
|
next_token,
|
||||||
|
) = route53_backend.list_query_logging_configs(
|
||||||
|
hosted_zone_id=hosted_zone_id,
|
||||||
|
next_token=next_token,
|
||||||
|
max_results=max_results,
|
||||||
|
)
|
||||||
|
except InvalidToken as exc:
|
||||||
|
raise InvalidPaginationToken() from exc
|
||||||
|
except Route53ClientError as r53error:
|
||||||
|
return r53error.code, {}, r53error.description
|
||||||
|
|
||||||
|
template = Template(LIST_QUERY_LOGGING_CONFIGS_RESPONSE)
|
||||||
|
return (
|
||||||
|
200,
|
||||||
|
headers,
|
||||||
|
template.render(
|
||||||
|
query_logging_configs=all_configs,
|
||||||
|
next_token=next_token,
|
||||||
|
xmlns=XMLNS,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_or_delete_query_logging_config_response(self, request, full_url, headers):
|
||||||
|
self.setup_class(request, full_url, headers)
|
||||||
|
parsed_url = urlparse(full_url)
|
||||||
|
query_logging_config_id = parsed_url.path.rstrip("/").rsplit("/", 1)[1]
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
try:
|
||||||
|
query_logging_config = route53_backend.get_query_logging_config(
|
||||||
|
query_logging_config_id
|
||||||
|
)
|
||||||
|
except Route53ClientError as r53error:
|
||||||
|
return r53error.code, {}, r53error.description
|
||||||
|
template = Template(GET_QUERY_LOGGING_CONFIG_RESPONSE)
|
||||||
|
return (
|
||||||
|
200,
|
||||||
|
headers,
|
||||||
|
template.render(query_logging_config=query_logging_config, xmlns=XMLNS),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif request.method == "DELETE":
|
||||||
|
try:
|
||||||
|
route53_backend.delete_query_logging_config(query_logging_config_id)
|
||||||
|
except Route53ClientError as r53error:
|
||||||
|
return r53error.code, {}, r53error.description
|
||||||
|
return 200, headers, ""
|
||||||
|
|
||||||
|
|
||||||
def no_such_hosted_zone_error(zoneid, headers={}):
|
def no_such_hosted_zone_error(zoneid, headers=None):
|
||||||
|
if not headers:
|
||||||
|
headers = {}
|
||||||
headers["X-Amzn-ErrorType"] = "NoSuchHostedZone"
|
headers["X-Amzn-ErrorType"] = "NoSuchHostedZone"
|
||||||
headers["Content-Type"] = "text/xml"
|
headers["Content-Type"] = "text/xml"
|
||||||
message = "Zone %s Not Found" % zoneid
|
error_response = f"""<ErrorResponse xmlns="{XMLNS}">
|
||||||
error_response = (
|
<Error>
|
||||||
"<Error><Code>NoSuchHostedZone</Code><Message>%s</Message></Error>" % message
|
<Code>NoSuchHostedZone</Code>
|
||||||
)
|
<Message>Zone {zoneid} Not Found</Message>
|
||||||
error_response = '<ErrorResponse xmlns="%s">%s</ErrorResponse>' % (
|
</Error>
|
||||||
XMLNS,
|
</ErrorResponse>"""
|
||||||
error_response,
|
|
||||||
)
|
|
||||||
return 404, headers, error_response
|
return 404, headers, error_response
|
||||||
|
|
||||||
|
|
||||||
@ -323,7 +413,7 @@ LIST_HOSTED_ZONES_RESPONSE = """<ListHostedZonesResponse xmlns="https://route53.
|
|||||||
<IsTruncated>false</IsTruncated>
|
<IsTruncated>false</IsTruncated>
|
||||||
</ListHostedZonesResponse>"""
|
</ListHostedZonesResponse>"""
|
||||||
|
|
||||||
LIST_HOSTED_ZONES_BY_NAME_RESPONSE = """<ListHostedZonesByNameResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
|
LIST_HOSTED_ZONES_BY_NAME_RESPONSE = """<ListHostedZonesByNameResponse xmlns="{{ xmlns }}">
|
||||||
{% if dnsname %}
|
{% if dnsname %}
|
||||||
<DNSName>{{ dnsname }}</DNSName>
|
<DNSName>{{ dnsname }}</DNSName>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -346,12 +436,12 @@ LIST_HOSTED_ZONES_BY_NAME_RESPONSE = """<ListHostedZonesByNameResponse xmlns="ht
|
|||||||
</ListHostedZonesByNameResponse>"""
|
</ListHostedZonesByNameResponse>"""
|
||||||
|
|
||||||
CREATE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
CREATE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<CreateHealthCheckResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
|
<CreateHealthCheckResponse xmlns="{{ xmlns }}">
|
||||||
{{ health_check.to_xml() }}
|
{{ health_check.to_xml() }}
|
||||||
</CreateHealthCheckResponse>"""
|
</CreateHealthCheckResponse>"""
|
||||||
|
|
||||||
LIST_HEALTH_CHECKS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
LIST_HEALTH_CHECKS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ListHealthChecksResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
|
<ListHealthChecksResponse xmlns="{{ xmlns }}">
|
||||||
<HealthChecks>
|
<HealthChecks>
|
||||||
{% for health_check in health_checks %}
|
{% for health_check in health_checks %}
|
||||||
{{ health_check.to_xml() }}
|
{{ health_check.to_xml() }}
|
||||||
@ -362,14 +452,36 @@ LIST_HEALTH_CHECKS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
</ListHealthChecksResponse>"""
|
</ListHealthChecksResponse>"""
|
||||||
|
|
||||||
DELETE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
DELETE_HEALTH_CHECK_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<DeleteHealthCheckResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
|
<DeleteHealthCheckResponse xmlns="{{ xmlns }}">
|
||||||
</DeleteHealthCheckResponse>"""
|
</DeleteHealthCheckResponse>"""
|
||||||
|
|
||||||
GET_CHANGE_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
GET_CHANGE_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<GetChangeResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
|
<GetChangeResponse xmlns="{{ xmlns }}">
|
||||||
<ChangeInfo>
|
<ChangeInfo>
|
||||||
<Status>INSYNC</Status>
|
<Status>INSYNC</Status>
|
||||||
<SubmittedAt>2010-09-10T01:36:41.958Z</SubmittedAt>
|
<SubmittedAt>2010-09-10T01:36:41.958Z</SubmittedAt>
|
||||||
<Id>{{ change_id }}</Id>
|
<Id>{{ change_id }}</Id>
|
||||||
</ChangeInfo>
|
</ChangeInfo>
|
||||||
</GetChangeResponse>"""
|
</GetChangeResponse>"""
|
||||||
|
|
||||||
|
CREATE_QUERY_LOGGING_CONFIG_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<CreateQueryLoggingConfigResponse xmlns="{{ xmlns }}">
|
||||||
|
{{ query_logging_config.to_xml() }}
|
||||||
|
</CreateQueryLoggingConfigResponse>"""
|
||||||
|
|
||||||
|
GET_QUERY_LOGGING_CONFIG_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<CreateQueryLoggingConfigResponse xmlns="{{ xmlns }}">
|
||||||
|
{{ query_logging_config.to_xml() }}
|
||||||
|
</CreateQueryLoggingConfigResponse>"""
|
||||||
|
|
||||||
|
LIST_QUERY_LOGGING_CONFIGS_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ListQueryLoggingConfigsResponse xmlns="{{ xmlns }}">
|
||||||
|
<QueryLoggingConfigs>
|
||||||
|
{% for query_logging_config in query_logging_configs %}
|
||||||
|
{{ query_logging_config.to_xml() }}
|
||||||
|
{% endfor %}
|
||||||
|
</QueryLoggingConfigs>
|
||||||
|
{% if next_token %}
|
||||||
|
<NextToken>{{ next_token }}</NextToken>
|
||||||
|
{% endif %}
|
||||||
|
</ListQueryLoggingConfigsResponse>"""
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
"""Route53 base URL and path."""
|
||||||
from .responses import Route53
|
from .responses import Route53
|
||||||
|
|
||||||
url_bases = [r"https?://route53(.*)\.amazonaws.com"]
|
url_bases = [r"https?://route53(.*)\.amazonaws.com"]
|
||||||
@ -22,4 +23,6 @@ url_paths = {
|
|||||||
r"{0}/(?P<api_version>[\d_-]+)/tags/hostedzone/(?P<zone_id>[^/]+)$": tag_response2,
|
r"{0}/(?P<api_version>[\d_-]+)/tags/hostedzone/(?P<zone_id>[^/]+)$": tag_response2,
|
||||||
r"{0}/(?P<api_version>[\d_-]+)/trafficpolicyinstances/*": Route53().not_implemented_response,
|
r"{0}/(?P<api_version>[\d_-]+)/trafficpolicyinstances/*": Route53().not_implemented_response,
|
||||||
r"{0}/(?P<api_version>[\d_-]+)/change/(?P<change_id>[^/]+)$": Route53().get_change,
|
r"{0}/(?P<api_version>[\d_-]+)/change/(?P<change_id>[^/]+)$": Route53().get_change,
|
||||||
|
r"{0}/(?P<api_version>[\d_-]+)/queryloggingconfig$": Route53().list_or_create_query_logging_config_response,
|
||||||
|
r"{0}/(?P<api_version>[\d_-]+)/queryloggingconfig/(?P<query_id>[^/]+)$": Route53().get_or_delete_query_logging_config_response,
|
||||||
}
|
}
|
||||||
|
10
moto/route53/utils.py
Normal file
10
moto/route53/utils.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""Pagination control model for Route53."""
|
||||||
|
|
||||||
|
PAGINATION_MODEL = {
|
||||||
|
"list_query_logging_configs": {
|
||||||
|
"input_token": "next_token",
|
||||||
|
"limit_key": "max_results",
|
||||||
|
"limit_default": 100,
|
||||||
|
"page_ending_range_keys": ["hosted_zone_id"],
|
||||||
|
},
|
||||||
|
}
|
277
tests/test_route53/test_route53_query_logging_config.py
Normal file
277
tests/test_route53/test_route53_query_logging_config.py
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
"""Route53 unit tests specific to query_logging_config APIs."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
|
from moto import mock_logs
|
||||||
|
from moto import mock_route53
|
||||||
|
from moto.core import ACCOUNT_ID
|
||||||
|
from moto.core.utils import get_random_hex
|
||||||
|
|
||||||
|
# The log group must be in the us-east-1 region.
|
||||||
|
TEST_REGION = "us-east-1"
|
||||||
|
|
||||||
|
|
||||||
|
def create_hosted_zone_id(route53_client, hosted_zone_test_name):
|
||||||
|
"""Return ID of a newly created Route53 public hosted zone"""
|
||||||
|
response = route53_client.create_hosted_zone(
|
||||||
|
Name=hosted_zone_test_name,
|
||||||
|
CallerReference=f"test_caller_ref_{get_random_hex(6)}",
|
||||||
|
)
|
||||||
|
assert response["ResponseMetadata"]["HTTPStatusCode"] == 201
|
||||||
|
assert "HostedZone" in response and response["HostedZone"]["Id"]
|
||||||
|
return response["HostedZone"]["Id"]
|
||||||
|
|
||||||
|
|
||||||
|
def create_log_group_arn(logs_client, hosted_zone_test_name):
|
||||||
|
"""Return ARN of a newly created CloudWatch log group."""
|
||||||
|
log_group_name = f"/aws/route53/{hosted_zone_test_name}"
|
||||||
|
response = logs_client.create_log_group(logGroupName=log_group_name)
|
||||||
|
assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
|
||||||
|
log_group_arn = None
|
||||||
|
response = logs_client.describe_log_groups()
|
||||||
|
for entry in response["logGroups"]:
|
||||||
|
if entry["logGroupName"] == log_group_name:
|
||||||
|
log_group_arn = entry["arn"]
|
||||||
|
break
|
||||||
|
return log_group_arn
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_route53
|
||||||
|
def test_create_query_logging_config_bad_args():
|
||||||
|
"""Test bad arguments to create_query_logging_config()."""
|
||||||
|
client = boto3.client("route53", region_name=TEST_REGION)
|
||||||
|
logs_client = boto3.client("logs", region_name=TEST_REGION)
|
||||||
|
|
||||||
|
hosted_zone_test_name = f"route53_query_log_{get_random_hex(6)}.test"
|
||||||
|
hosted_zone_id = create_hosted_zone_id(client, hosted_zone_test_name)
|
||||||
|
log_group_arn = create_log_group_arn(logs_client, hosted_zone_test_name)
|
||||||
|
|
||||||
|
# Check exception: NoSuchHostedZone
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId="foo", CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "NoSuchHostedZone"
|
||||||
|
assert "No hosted zone found with ID: foo" in err["Message"]
|
||||||
|
|
||||||
|
# Check exception: InvalidInput (bad CloudWatch Logs log ARN)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id,
|
||||||
|
CloudWatchLogsLogGroupArn=f"arn:aws:logs:{TEST_REGION}:{ACCOUNT_ID}:foo-bar:foo",
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "InvalidInput"
|
||||||
|
assert "The ARN for the CloudWatch Logs log group is invalid" in err["Message"]
|
||||||
|
|
||||||
|
# Check exception: InvalidInput (CloudWatch Logs log not in us-east-1)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id,
|
||||||
|
CloudWatchLogsLogGroupArn=log_group_arn.replace(TEST_REGION, "us-west-1"),
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "InvalidInput"
|
||||||
|
assert "The ARN for the CloudWatch Logs log group is invalid" in err["Message"]
|
||||||
|
|
||||||
|
# Check exception: NoSuchCloudWatchLogsLogGroup
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id,
|
||||||
|
CloudWatchLogsLogGroupArn=log_group_arn.replace(
|
||||||
|
hosted_zone_test_name, "foo"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "NoSuchCloudWatchLogsLogGroup"
|
||||||
|
assert "The specified CloudWatch Logs log group doesn't exist" in err["Message"]
|
||||||
|
|
||||||
|
# Check exception: QueryLoggingConfigAlreadyExists
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "QueryLoggingConfigAlreadyExists"
|
||||||
|
assert (
|
||||||
|
"A query logging configuration already exists for this hosted zone"
|
||||||
|
in err["Message"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_route53
|
||||||
|
def test_create_query_logging_config_good_args():
|
||||||
|
"""Test a valid create_logging_config() request."""
|
||||||
|
client = boto3.client("route53", region_name=TEST_REGION)
|
||||||
|
logs_client = boto3.client("logs", region_name=TEST_REGION)
|
||||||
|
|
||||||
|
hosted_zone_test_name = f"route53_query_log_{get_random_hex(6)}.test"
|
||||||
|
hosted_zone_id = create_hosted_zone_id(client, hosted_zone_test_name)
|
||||||
|
log_group_arn = create_log_group_arn(logs_client, hosted_zone_test_name)
|
||||||
|
|
||||||
|
response = client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
config = response["QueryLoggingConfig"]
|
||||||
|
assert config["HostedZoneId"] == hosted_zone_id.split("/")[-1]
|
||||||
|
assert config["CloudWatchLogsLogGroupArn"] == log_group_arn
|
||||||
|
assert config["Id"]
|
||||||
|
|
||||||
|
location = response["Location"]
|
||||||
|
assert (
|
||||||
|
location
|
||||||
|
== f"https://route53.amazonaws.com/2013-04-01/queryloggingconfig/{config['Id']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_route53
|
||||||
|
def test_delete_query_logging_config():
|
||||||
|
"""Test valid and invalid delete_query_logging_config requests."""
|
||||||
|
client = boto3.client("route53", region_name=TEST_REGION)
|
||||||
|
logs_client = boto3.client("logs", region_name=TEST_REGION)
|
||||||
|
|
||||||
|
# Create a query logging config that can then be deleted.
|
||||||
|
hosted_zone_test_name = f"route53_query_log_{get_random_hex(6)}.test"
|
||||||
|
hosted_zone_id = create_hosted_zone_id(client, hosted_zone_test_name)
|
||||||
|
log_group_arn = create_log_group_arn(logs_client, hosted_zone_test_name)
|
||||||
|
|
||||||
|
query_response = client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test the deletion.
|
||||||
|
query_id = query_response["QueryLoggingConfig"]["Id"]
|
||||||
|
response = client.delete_query_logging_config(Id=query_id)
|
||||||
|
# There is no response other than the usual ResponseMetadata.
|
||||||
|
assert list(response.keys()) == ["ResponseMetadata"]
|
||||||
|
|
||||||
|
# Test the deletion of a non-existent query logging config, i.e., the
|
||||||
|
# one that was just deleted.
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.delete_query_logging_config(Id=query_id)
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "NoSuchQueryLoggingConfig"
|
||||||
|
assert "The query logging configuration does not exist" in err["Message"]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_route53
|
||||||
|
def test_get_query_logging_config():
|
||||||
|
"""Test valid and invalid get_query_logging_config requests."""
|
||||||
|
client = boto3.client("route53", region_name=TEST_REGION)
|
||||||
|
logs_client = boto3.client("logs", region_name=TEST_REGION)
|
||||||
|
|
||||||
|
# Create a query logging config that can then be retrieved.
|
||||||
|
hosted_zone_test_name = f"route53_query_log_{get_random_hex(6)}.test"
|
||||||
|
hosted_zone_id = create_hosted_zone_id(client, hosted_zone_test_name)
|
||||||
|
log_group_arn = create_log_group_arn(logs_client, hosted_zone_test_name)
|
||||||
|
|
||||||
|
query_response = client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test the retrieval.
|
||||||
|
query_id = query_response["QueryLoggingConfig"]["Id"]
|
||||||
|
response = client.get_query_logging_config(Id=query_id)
|
||||||
|
config = response["QueryLoggingConfig"]
|
||||||
|
assert config["HostedZoneId"] == hosted_zone_id.split("/")[-1]
|
||||||
|
assert config["CloudWatchLogsLogGroupArn"] == log_group_arn
|
||||||
|
assert config["Id"]
|
||||||
|
|
||||||
|
# Test the retrieval of a non-existent query logging config.
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.get_query_logging_config(Id="1234567890")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "NoSuchQueryLoggingConfig"
|
||||||
|
assert "The query logging configuration does not exist" in err["Message"]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_route53
|
||||||
|
def test_list_query_logging_configs_bad_args():
|
||||||
|
"""Test bad arguments to list_query_logging_configs()."""
|
||||||
|
client = boto3.client("route53", region_name=TEST_REGION)
|
||||||
|
logs_client = boto3.client("logs", region_name=TEST_REGION)
|
||||||
|
|
||||||
|
# Check exception: NoSuchHostedZone
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.list_query_logging_configs(HostedZoneId="foo", MaxResults="10")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "NoSuchHostedZone"
|
||||||
|
assert "No hosted zone found with ID: foo" in err["Message"]
|
||||||
|
|
||||||
|
# Create a couple of query logging configs to work with.
|
||||||
|
for _ in range(3):
|
||||||
|
hosted_zone_test_name = f"route53_query_log_{get_random_hex(6)}.test"
|
||||||
|
hosted_zone_id = create_hosted_zone_id(client, hosted_zone_test_name)
|
||||||
|
log_group_arn = create_log_group_arn(logs_client, hosted_zone_test_name)
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retrieve a query logging config, then request more with an invalid token.
|
||||||
|
client.list_query_logging_configs(MaxResults="1")
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.list_query_logging_configs(NextToken="foo")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
assert err["Code"] == "InvalidPaginationToken"
|
||||||
|
assert (
|
||||||
|
"Route 53 can't get the next page of query logging configurations "
|
||||||
|
"because the specified value for NextToken is invalid." in err["Message"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_logs
|
||||||
|
@mock_route53
|
||||||
|
def test_list_query_logging_configs_good_args():
|
||||||
|
"""Test valid arguments to list_query_logging_configs()."""
|
||||||
|
client = boto3.client("route53", region_name=TEST_REGION)
|
||||||
|
logs_client = boto3.client("logs", region_name=TEST_REGION)
|
||||||
|
|
||||||
|
# Test when there are no query logging configs.
|
||||||
|
response = client.list_query_logging_configs()
|
||||||
|
query_logging_configs = response["QueryLoggingConfigs"]
|
||||||
|
assert len(query_logging_configs) == 0
|
||||||
|
|
||||||
|
# Create a couple of query logging configs to work with.
|
||||||
|
zone_ids = []
|
||||||
|
for _ in range(10):
|
||||||
|
hosted_zone_test_name = f"route53_query_log_{get_random_hex(6)}.test"
|
||||||
|
hosted_zone_id = create_hosted_zone_id(client, hosted_zone_test_name)
|
||||||
|
zone_ids.append(hosted_zone_id)
|
||||||
|
|
||||||
|
log_group_arn = create_log_group_arn(logs_client, hosted_zone_test_name)
|
||||||
|
client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify all 10 of the query logging configs can be retrieved in one go.
|
||||||
|
response = client.list_query_logging_configs()
|
||||||
|
query_logging_configs = response["QueryLoggingConfigs"]
|
||||||
|
assert len(query_logging_configs) == 10
|
||||||
|
for idx, query_logging_config in enumerate(query_logging_configs):
|
||||||
|
assert query_logging_config["HostedZoneId"] == zone_ids[idx].split("/")[-1]
|
||||||
|
|
||||||
|
# Request only two of the query logging configs and verify there's a
|
||||||
|
# next_token.
|
||||||
|
response = client.list_query_logging_configs(MaxResults="2")
|
||||||
|
assert len(response["QueryLoggingConfigs"]) == 2
|
||||||
|
assert response["NextToken"]
|
||||||
|
|
||||||
|
# Request the remaining 8 query logging configs and verify there is
|
||||||
|
# no next token.
|
||||||
|
response = client.list_query_logging_configs(
|
||||||
|
MaxResults="8", NextToken=response["NextToken"]
|
||||||
|
)
|
||||||
|
assert len(response["QueryLoggingConfigs"]) == 8
|
||||||
|
assert "NextToken" not in response
|
Loading…
Reference in New Issue
Block a user