Skip to content

Commit 2787cc1

Browse files
Add Region & SetIdentifier properties to Route53 to enable latency based routing (#2998)
Co-authored-by: Christoffer Rehn <[email protected]>
1 parent 63280e5 commit 2787cc1

File tree

28 files changed

+3252
-15
lines changed

28 files changed

+3252
-15
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from unittest.case import skipIf
2+
3+
from integration.config.service_names import CUSTOM_DOMAIN
4+
from integration.helpers.base_internal_test import BaseInternalTest
5+
from integration.helpers.resource import current_region_not_included
6+
7+
8+
@skipIf(
9+
current_region_not_included([CUSTOM_DOMAIN]),
10+
"CustomDomain is not supported in this testing region",
11+
)
12+
class TestCustomHttpApiDomainsLatencyRouting(BaseInternalTest):
13+
def test_custom_http_api_domains_regional(self):
14+
self.create_and_verify_stack("combination/api_with_custom_domains_regional_latency_routing")
15+
16+
route53_list = self.get_stack_resources("AWS::Route53::RecordSetGroup")
17+
self.assertEqual(1, len(route53_list))
18+
19+
client = self.client_provider.route53_client
20+
result = client.list_resource_record_sets(HostedZoneId="xyz")
21+
record_set_list = result["ResourceRecordSets"]
22+
record_set = next(r for r in record_set_list if r["Name"] == "test.domain.com" & r["Type"] == "A")
23+
24+
self.assertIsNotNone(record_set["SetIdentifier"])
25+
self.assertIsNotNone(record_set["Region"])
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from unittest.case import skipIf
2+
3+
from integration.config.service_names import CUSTOM_DOMAIN
4+
from integration.helpers.base_internal_test import BaseInternalTest
5+
from integration.helpers.resource import current_region_not_included
6+
7+
8+
@skipIf(
9+
current_region_not_included([CUSTOM_DOMAIN]),
10+
"CustomDomain is not supported in this testing region",
11+
)
12+
class TestCustomHttpApiDomainsLatencyRoutingIpV6(BaseInternalTest):
13+
def test_custom_http_api_domains_regional(self):
14+
self.create_and_verify_stack("combination/api_with_custom_domains_regional_latency_routing_ipv6")
15+
16+
route53_list = self.get_stack_resources("AWS::Route53::RecordSetGroup")
17+
self.assertEqual(1, len(route53_list))
18+
19+
client = self.client_provider.route53_client
20+
result = client.list_resource_record_sets(HostedZoneId="xyz")
21+
record_set_list = result["ResourceRecordSets"]
22+
record_set = next(r for r in record_set_list if r["Name"] == "test.domain.com" & r["Type"] == "AAAA")
23+
24+
self.assertIsNotNone(record_set["SetIdentifier"])
25+
self.assertIsNotNone(record_set["Region"])
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[
2+
{
3+
"LogicalResourceId": "MyFunctionImplicitGetPermission",
4+
"ResourceType": "AWS::Lambda::Permission"
5+
},
6+
{
7+
"LogicalResourceId": "MyFunctionImplicitPostPermission",
8+
"ResourceType": "AWS::Lambda::Permission"
9+
},
10+
{
11+
"LogicalResourceId": "MyApipostApiMapping",
12+
"ResourceType": "AWS::ApiGatewayV2::ApiMapping"
13+
},
14+
{
15+
"LogicalResourceId": "MyApigetApiMapping",
16+
"ResourceType": "AWS::ApiGatewayV2::ApiMapping"
17+
},
18+
{
19+
"LogicalResourceId": "MyApi",
20+
"ResourceType": "AWS::ApiGatewayV2::Api"
21+
},
22+
{
23+
"LogicalResourceId": "RecordSetGroupddfc299be2",
24+
"ResourceType": "AWS::Route53::RecordSetGroup"
25+
},
26+
{
27+
"LogicalResourceId": "MyApiProdStage",
28+
"ResourceType": "AWS::ApiGatewayV2::Stage"
29+
},
30+
{
31+
"LogicalResourceId": "ApiGatewayDomainNameV2e7a0af471b",
32+
"ResourceType": "AWS::ApiGatewayV2::DomainName"
33+
},
34+
{
35+
"LogicalResourceId": "MyFunction",
36+
"ResourceType": "AWS::Lambda::Function"
37+
},
38+
{
39+
"LogicalResourceId": "MyFunctionRole",
40+
"ResourceType": "AWS::IAM::Role"
41+
}
42+
]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[
2+
{
3+
"LogicalResourceId": "MyFunctionImplicitGetPermission",
4+
"ResourceType": "AWS::Lambda::Permission"
5+
},
6+
{
7+
"LogicalResourceId": "MyFunctionImplicitPostPermission",
8+
"ResourceType": "AWS::Lambda::Permission"
9+
},
10+
{
11+
"LogicalResourceId": "MyApipostApiMapping",
12+
"ResourceType": "AWS::ApiGatewayV2::ApiMapping"
13+
},
14+
{
15+
"LogicalResourceId": "MyApigetApiMapping",
16+
"ResourceType": "AWS::ApiGatewayV2::ApiMapping"
17+
},
18+
{
19+
"LogicalResourceId": "MyApi",
20+
"ResourceType": "AWS::ApiGatewayV2::Api"
21+
},
22+
{
23+
"LogicalResourceId": "RecordSetGroupddfc299be2",
24+
"ResourceType": "AWS::Route53::RecordSetGroup"
25+
},
26+
{
27+
"LogicalResourceId": "MyApiProdStage",
28+
"ResourceType": "AWS::ApiGatewayV2::Stage"
29+
},
30+
{
31+
"LogicalResourceId": "ApiGatewayDomainNameV2e7a0af471b",
32+
"ResourceType": "AWS::ApiGatewayV2::DomainName"
33+
},
34+
{
35+
"LogicalResourceId": "MyFunction",
36+
"ResourceType": "AWS::Lambda::Function"
37+
},
38+
{
39+
"LogicalResourceId": "MyFunctionRole",
40+
"ResourceType": "AWS::IAM::Role"
41+
}
42+
]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Parameters:
2+
MyRestRegionalDomainName:
3+
Type: String
4+
MyRestRegionalDomainCert:
5+
Type: String
6+
HostedZoneId:
7+
Type: String
8+
9+
Globals:
10+
Api:
11+
Domain:
12+
DomainName:
13+
Ref: MyRestRegionalDomainName
14+
CertificateArn:
15+
Ref: MyRestRegionalDomainCert
16+
EndpointConfiguration: REGIONAL
17+
MutualTlsAuthentication:
18+
TruststoreUri: ${mtlsuri}
19+
TruststoreVersion: 0
20+
SecurityPolicy: TLS_1_2
21+
BasePath:
22+
- /get
23+
- /post
24+
Route53:
25+
HostedZoneId:
26+
Ref: HostedZoneId
27+
Region: eu-west-2
28+
SetIdentifier: eu-west-2
29+
DistributionDomainName: test.domain.com
30+
31+
Resources:
32+
MyFunction:
33+
Type: AWS::Serverless::Function
34+
Properties:
35+
InlineCode: |
36+
exports.handler = async (event) => {
37+
const response = {
38+
statusCode: 200,
39+
body: JSON.stringify('Hello from Lambda!'),
40+
};
41+
return response;
42+
};
43+
Handler: index.handler
44+
Runtime: nodejs14.x
45+
Events:
46+
ImplicitGet:
47+
Type: Api
48+
Properties:
49+
Method: Get
50+
Path: /get
51+
ImplicitPost:
52+
Type: Api
53+
Properties:
54+
Method: Post
55+
Path: /post
56+
Metadata:
57+
SamTransformTest: true
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
Parameters:
2+
MyRestRegionalDomainName:
3+
Type: String
4+
MyRestRegionalDomainCert:
5+
Type: String
6+
HostedZoneId:
7+
Type: String
8+
9+
Globals:
10+
Api:
11+
Domain:
12+
DomainName:
13+
Ref: MyRestRegionalDomainName
14+
CertificateArn:
15+
Ref: MyRestRegionalDomainCert
16+
EndpointConfiguration: REGIONAL
17+
MutualTlsAuthentication:
18+
TruststoreUri: ${mtlsuri}
19+
TruststoreVersion: 0
20+
SecurityPolicy: TLS_1_2
21+
BasePath:
22+
- /get
23+
- /post
24+
Route53:
25+
HostedZoneId:
26+
Ref: HostedZoneId
27+
Region: eu-west-2
28+
SetIdentifier: eu-west-2
29+
DistributionDomainName: test.domain.com
30+
IpV6: true
31+
32+
Resources:
33+
MyFunction:
34+
Type: AWS::Serverless::Function
35+
Properties:
36+
InlineCode: |
37+
exports.handler = async (event) => {
38+
const response = {
39+
statusCode: 200,
40+
body: JSON.stringify('Hello from Lambda!'),
41+
};
42+
return response;
43+
};
44+
Handler: index.handler
45+
Runtime: nodejs14.x
46+
Events:
47+
ImplicitGet:
48+
Type: Api
49+
Properties:
50+
Method: Get
51+
Path: /get
52+
ImplicitPost:
53+
Type: Api
54+
Properties:
55+
Method: Post
56+
Path: /post
57+
Metadata:
58+
SamTransformTest: true

samtranslator/internal/schema_source/aws_serverless_api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ class Route53(BaseModel):
132132
HostedZoneId: Optional[PassThroughProp] = route53("HostedZoneId")
133133
HostedZoneName: Optional[PassThroughProp] = route53("HostedZoneName")
134134
IpV6: Optional[bool] = route53("IpV6")
135+
SetIdentifier: Optional[PassThroughProp] # TODO: add docs
136+
Region: Optional[PassThroughProp] # TODO: add docs
135137

136138

137139
class Domain(BaseModel):

samtranslator/internal/schema_source/aws_serverless_httpapi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class Route53(BaseModel):
8787
HostedZoneId: Optional[PassThroughProp] = route53("HostedZoneId")
8888
HostedZoneName: Optional[PassThroughProp] = route53("HostedZoneName")
8989
IpV6: Optional[bool] = route53("IpV6")
90+
SetIdentifier: Optional[PassThroughProp] # TODO: add docs
91+
Region: Optional[PassThroughProp] # TODO: add docs
9092

9193

9294
class Domain(BaseModel):

samtranslator/model/api/api_generator.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -579,25 +579,36 @@ def _construct_api_domain( # noqa: too-many-branches
579579
return domain, basepath_resource_list, record_set_group
580580

581581
def _construct_record_sets_for_domain(
582-
self, domain: Dict[str, Any], api_domain_name: str, route53: Any
582+
self, custom_domain_config: Dict[str, Any], api_domain_name: str, route53_config: Dict[str, Any]
583583
) -> List[Dict[str, Any]]:
584584
recordset_list = []
585-
recordset = {}
586585

587-
recordset["Name"] = domain.get("DomainName")
586+
recordset = {}
587+
recordset["Name"] = custom_domain_config.get("DomainName")
588588
recordset["Type"] = "A"
589-
recordset["AliasTarget"] = self._construct_alias_target(domain, api_domain_name, route53)
590-
recordset_list.extend([recordset])
589+
recordset["AliasTarget"] = self._construct_alias_target(custom_domain_config, api_domain_name, route53_config)
590+
self._update_route53_routing_policy_properties(route53_config, recordset)
591+
recordset_list.append(recordset)
591592

592-
recordset_ipv6 = {}
593-
if route53.get("IpV6") is not None and route53.get("IpV6") is True:
594-
recordset_ipv6["Name"] = domain.get("DomainName")
593+
if route53_config.get("IpV6") is not None and route53_config.get("IpV6") is True:
594+
recordset_ipv6 = {}
595+
recordset_ipv6["Name"] = custom_domain_config.get("DomainName")
595596
recordset_ipv6["Type"] = "AAAA"
596-
recordset_ipv6["AliasTarget"] = self._construct_alias_target(domain, api_domain_name, route53)
597-
recordset_list.extend([recordset_ipv6])
597+
recordset_ipv6["AliasTarget"] = self._construct_alias_target(
598+
custom_domain_config, api_domain_name, route53_config
599+
)
600+
self._update_route53_routing_policy_properties(route53_config, recordset_ipv6)
601+
recordset_list.append(recordset_ipv6)
598602

599603
return recordset_list
600604

605+
@staticmethod
606+
def _update_route53_routing_policy_properties(route53_config: Dict[str, Any], recordset: Dict[str, Any]) -> None:
607+
if route53_config.get("Region") is not None:
608+
recordset["Region"] = route53_config.get("Region")
609+
if route53_config.get("SetIdentifier") is not None:
610+
recordset["SetIdentifier"] = route53_config.get("SetIdentifier")
611+
601612
def _construct_alias_target(self, domain: Dict[str, Any], api_domain_name: str, route53: Any) -> Dict[str, Any]:
602613
alias_target = {}
603614
target_health = route53.get("EvaluateTargetHealth")

samtranslator/model/api/http_api_generator.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -417,24 +417,33 @@ def _construct_record_sets_for_domain(
417417
self, custom_domain_config: Dict[str, Any], route53_config: Dict[str, Any], api_domain_name: str
418418
) -> List[Dict[str, Any]]:
419419
recordset_list = []
420-
recordset = {}
421420

421+
recordset = {}
422422
recordset["Name"] = custom_domain_config.get("DomainName")
423423
recordset["Type"] = "A"
424424
recordset["AliasTarget"] = self._construct_alias_target(custom_domain_config, route53_config, api_domain_name)
425-
recordset_list.extend([recordset])
425+
self._update_route53_routing_policy_properties(route53_config, recordset)
426+
recordset_list.append(recordset)
426427

427-
recordset_ipv6 = {}
428-
if route53_config.get("IpV6"):
428+
if route53_config.get("IpV6") is not None and route53_config.get("IpV6") is True:
429+
recordset_ipv6 = {}
429430
recordset_ipv6["Name"] = custom_domain_config.get("DomainName")
430431
recordset_ipv6["Type"] = "AAAA"
431432
recordset_ipv6["AliasTarget"] = self._construct_alias_target(
432433
custom_domain_config, route53_config, api_domain_name
433434
)
434-
recordset_list.extend([recordset_ipv6])
435+
self._update_route53_routing_policy_properties(route53_config, recordset_ipv6)
436+
recordset_list.append(recordset_ipv6)
435437

436438
return recordset_list
437439

440+
@staticmethod
441+
def _update_route53_routing_policy_properties(route53_config: Dict[str, Any], recordset: Dict[str, Any]) -> None:
442+
if route53_config.get("Region") is not None:
443+
recordset["Region"] = route53_config.get("Region")
444+
if route53_config.get("SetIdentifier") is not None:
445+
recordset["SetIdentifier"] = route53_config.get("SetIdentifier")
446+
438447
def _construct_alias_target(
439448
self, domain_config: Dict[str, Any], route53_config: Dict[str, Any], api_domain_name: str
440449
) -> Dict[str, Any]:

0 commit comments

Comments
 (0)