diff --git a/charts/ambassador/crds/getambassador.io_mappings.yaml b/charts/ambassador/crds/getambassador.io_mappings.yaml index e3007a5095..cc9ea3234a 100644 --- a/charts/ambassador/crds/getambassador.io_mappings.yaml +++ b/charts/ambassador/crds/getambassador.io_mappings.yaml @@ -371,6 +371,8 @@ spec: - type: array resolver: type: string + respect_dns_ttl: + type: boolean retry_policy: properties: num_retries: diff --git a/docs/yaml/ambassador/ambassador-crds.yaml b/docs/yaml/ambassador/ambassador-crds.yaml index b1588dc548..4ae37a938b 100644 --- a/docs/yaml/ambassador/ambassador-crds.yaml +++ b/docs/yaml/ambassador/ambassador-crds.yaml @@ -1078,6 +1078,8 @@ spec: - type: array resolver: type: string + respect_dns_ttl: + type: boolean retry_policy: properties: num_retries: diff --git a/docs/yaml/ambassador/ambassador-rbac-prometheus.yaml b/docs/yaml/ambassador/ambassador-rbac-prometheus.yaml index bbed81eab5..3f95ae3085 100644 --- a/docs/yaml/ambassador/ambassador-rbac-prometheus.yaml +++ b/docs/yaml/ambassador/ambassador-rbac-prometheus.yaml @@ -1142,6 +1142,8 @@ spec: - type: array resolver: type: string + respect_dns_ttl: + type: boolean retry_policy: properties: num_retries: diff --git a/manifests/ambassador/ambassador-crds.yaml b/manifests/ambassador/ambassador-crds.yaml index b1588dc548..4ae37a938b 100644 --- a/manifests/ambassador/ambassador-crds.yaml +++ b/manifests/ambassador/ambassador-crds.yaml @@ -1078,6 +1078,8 @@ spec: - type: array resolver: type: string + respect_dns_ttl: + type: boolean retry_policy: properties: num_retries: diff --git a/pkg/api/getambassador.io/v2/mapping_types.go b/pkg/api/getambassador.io/v2/mapping_types.go index a88f5866fc..3802a0387e 100644 --- a/pkg/api/getambassador.io/v2/mapping_types.go +++ b/pkg/api/getambassador.io/v2/mapping_types.go @@ -49,6 +49,7 @@ type MappingSpec struct { KeepAlive *KeepAlive `json:"keepalive,omitempty"` CORS *CORS `json:"cors,omitempty"` RetryPolicy *RetryPolicy `json:"retry_policy,omitempty"` + RespectDNSTTL *bool `json:"respect_dns_ttl,omitempty"` GRPC *bool `json:"grpc,omitempty"` HostRedirect *bool `json:"host_redirect,omitempty"` HostRewrite string `json:"host_rewrite,omitempty"` diff --git a/pkg/api/getambassador.io/v2/zz_generated.deepcopy.go b/pkg/api/getambassador.io/v2/zz_generated.deepcopy.go index cc1184813a..4c30f50ebb 100644 --- a/pkg/api/getambassador.io/v2/zz_generated.deepcopy.go +++ b/pkg/api/getambassador.io/v2/zz_generated.deepcopy.go @@ -1696,6 +1696,11 @@ func (in *MappingSpec) DeepCopyInto(out *MappingSpec) { *out = new(RetryPolicy) (*in).DeepCopyInto(*out) } + if in.RespectDNSTTL != nil { + in, out := &in.RespectDNSTTL, &out.RespectDNSTTL + *out = new(bool) + **out = **in + } if in.GRPC != nil { in, out := &in.GRPC, &out.GRPC *out = new(bool) diff --git a/python/ambassador/envoy/v2/v2cluster.py b/python/ambassador/envoy/v2/v2cluster.py index 7b5228599a..ff77d89568 100644 --- a/python/ambassador/envoy/v2/v2cluster.py +++ b/python/ambassador/envoy/v2/v2cluster.py @@ -67,6 +67,9 @@ def __init__(self, config: 'V2Config', cluster: IRCluster) -> None: 'dns_lookup_family': dns_lookup_family } + if cluster.respect_dns_ttl: + fields['respect_dns_ttl'] = cluster.respect_dns_ttl + if ctype == 'EDS': fields['eds_cluster_config'] = { 'eds_config': {'ads': {}}, 'service_name': cmap_entry['endpoint_path']} diff --git a/python/ambassador/envoy/v3/v3cluster.py b/python/ambassador/envoy/v3/v3cluster.py index 59fb006c5f..ef32ddc5e9 100644 --- a/python/ambassador/envoy/v3/v3cluster.py +++ b/python/ambassador/envoy/v3/v3cluster.py @@ -67,6 +67,9 @@ def __init__(self, config: 'V3Config', cluster: IRCluster) -> None: 'dns_lookup_family': dns_lookup_family } + if cluster.respect_dns_ttl: + fields['respect_dns_ttl'] = cluster.respect_dns_ttl + if ctype == 'EDS': fields['eds_cluster_config'] = { 'eds_config': { diff --git a/python/ambassador/ir/ircluster.py b/python/ambassador/ir/ircluster.py index 313be95509..860f988467 100644 --- a/python/ambassador/ir/ircluster.py +++ b/python/ambassador/ir/ircluster.py @@ -61,6 +61,7 @@ def __init__(self, ir: 'IR', aconf: Config, parent_ir_resource: 'IRResource', load_balancer: Optional[dict] = None, keepalive: Optional[dict] = None, circuit_breakers: Optional[list] = None, + respect_dns_ttl: Optional[bool] = False, rkey: str="-override-", kind: str="IRCluster", @@ -284,6 +285,7 @@ def __init__(self, ir: 'IR', aconf: Config, parent_ir_resource: 'IRResource', 'connect_timeout_ms': connect_timeout_ms, 'cluster_idle_timeout_ms': cluster_idle_timeout_ms, 'cluster_max_connection_lifetime_ms': cluster_max_connection_lifetime_ms, + 'respect_dns_ttl': respect_dns_ttl, } if grpc: diff --git a/python/ambassador/ir/irhttpmapping.py b/python/ambassador/ir/irhttpmapping.py index 1cfa4af281..6463b8a4f6 100644 --- a/python/ambassador/ir/irhttpmapping.py +++ b/python/ambassador/ir/irhttpmapping.py @@ -117,6 +117,7 @@ class IRHTTPMapping (IRBaseMapping): "remove_request_headers": True, "remove_response_headers": True, "resolver": False, + "respect_dns_ttl": False, "retry_policy": False, # Do not include rewrite "service": False, # See notes above diff --git a/python/ambassador/ir/irhttpmappinggroup.py b/python/ambassador/ir/irhttpmappinggroup.py index c983d83ea1..f1cf659309 100644 --- a/python/ambassador/ir/irhttpmappinggroup.py +++ b/python/ambassador/ir/irhttpmappinggroup.py @@ -250,7 +250,8 @@ def add_cluster_for_mapping(self, mapping: IRBaseMapping, cluster_idle_timeout_ms=mapping.get('cluster_idle_timeout_ms', None), cluster_max_connection_lifetime_ms=mapping.get('cluster_max_connection_lifetime_ms', None), circuit_breakers=mapping.get('circuit_breakers', None), - marker=marker) + marker=marker, + respect_dns_ttl=mapping.get('respect_dns_ttl', False)) # Make sure that the cluster is actually in our IR... stored = self.ir.add_cluster(cluster) diff --git a/python/schemas/v2/Mapping.schema b/python/schemas/v2/Mapping.schema index 665552690a..b2dc6f8675 100644 --- a/python/schemas/v2/Mapping.schema +++ b/python/schemas/v2/Mapping.schema @@ -207,6 +207,7 @@ "host_regex": { "type": "boolean" }, "headers": { "$ref": "#/definitions/mapStrStr" }, "regex_headers": { "$ref": "#/definitions/mapStrStr" }, + "respect_dns_ttl": { "type": "boolean" }, "labels": { "type": "object" }, diff --git a/python/tests/kat/t_dns_type.py b/python/tests/kat/t_dns_type.py index 844dd9cf12..c217729cca 100644 --- a/python/tests/kat/t_dns_type.py +++ b/python/tests/kat/t_dns_type.py @@ -54,3 +54,79 @@ def check(self): # Not getting a 400 bad request is confirmation that this setting works as long as the request can reach the upstream assert self.results[0].status == 200 +# Tests Respecting DNS TTL alone +class DnsTtl(AmbassadorTest): + target: ServiceType + + def init(self): + self.target = HTTP(name="target") + + def config(self): + yield self, self.format(""" +--- +apiVersion: ambassador/v2 +kind: Mapping +name: {self.target.path.k8s}-foo +prefix: /foo/ +service: {self.target.path.fqdn} +respect_dns_ttl: true +""") + + def queries(self): + yield Query(self.url("foo/")) + + def check(self): + # Not getting a 400 bad request is confirmation that this setting works as long as the request can reach the upstream + assert self.results[0].status == 200 + +# Tests the DNS TTL with the endpoint resolver +class DnsTtlEndpoint(AmbassadorTest): + target: ServiceType + + def init(self): + self.target = HTTP(name="target") + + def config(self): + yield self, self.format(""" +--- +apiVersion: ambassador/v2 +kind: Mapping +name: {self.target.path.k8s}-foo +prefix: /foo/ +service: {self.target.path.fqdn} +respect_dns_ttl: true +resolver: endpoint +""") + + def queries(self): + yield Query(self.url("foo/")) + + def check(self): + # Not getting a 400 bad request is confirmation that this setting works as long as the request can reach the upstream + assert self.results[0].status == 200 + +# Tests the DNS TTL with Logical DNS type +class DnsTtlDnsType(AmbassadorTest): + target: ServiceType + + def init(self): + self.target = HTTP(name="target") + + def config(self): + yield self, self.format(""" +--- +apiVersion: ambassador/v2 +kind: Mapping +name: {self.target.path.k8s}-foo +prefix: /foo/ +service: {self.target.path.fqdn} +respect_dns_ttl: true +dns_type: logical_dns +""") + + def queries(self): + yield Query(self.url("foo/")) + + def check(self): + # Not getting a 400 bad request is confirmation that this setting works as long as the request can reach the upstream + assert self.results[0].status == 200 diff --git a/python/tests/test_cluster_options.py b/python/tests/test_cluster_options.py index 761633eac8..620245ebf5 100644 --- a/python/tests/test_cluster_options.py +++ b/python/tests/test_cluster_options.py @@ -69,3 +69,20 @@ def test_logical_dns_type_wrong(): _test_cluster_setting(yaml, setting="type", expected="EDS", exists=True, envoy_version=v) +@pytest.mark.compilertest +def test_dns_ttl(): + # Test configuring the respect_dns_ttl generates an Envoy config + yaml = module_and_mapping_manifests(None, ["respect_dns_ttl: true"]) + for v in SUPPORTED_ENVOY_VERSIONS: + # The dns type is listed as just "type" + _test_cluster_setting(yaml, setting="respect_dns_ttl", + expected="true", exists=True, envoy_version=v) + +@pytest.mark.compilertest +def test_dns_ttl(): + # Test dns_ttl is not configured when not applied in the Mapping + yaml = module_and_mapping_manifests(None, None) + for v in SUPPORTED_ENVOY_VERSIONS: + # The dns type is listed as just "type" + _test_cluster_setting(yaml, setting="respect_dns_ttl", + expected="false", exists=False, envoy_version=v)