diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index 9468331583..38d3ba9c72 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -151,6 +151,12 @@ type BackendTrafficPolicySpec struct {
//
// +optional
RoutingType *RoutingType `json:"routingType,omitempty"`
+
+ // EndpointHostname configures the hostname value attached to backend endpoints.
+ // If unset, no hostname is attached to Kubernetes Service endpoints.
+ //
+ // +optional
+ EndpointHostname *BackendEndpointHostname `json:"endpointHostname,omitempty"`
}
type BackendTelemetry struct {
diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go
index 27c8f3d2d1..b8848a286e 100644
--- a/api/v1alpha1/shared_types.go
+++ b/api/v1alpha1/shared_types.go
@@ -693,6 +693,40 @@ type ClusterSettings struct {
HTTP2 *HTTP2Settings `json:"http2,omitempty"`
}
+// BackendEndpointHostnameType defines how endpoint hostnames should be populated.
+//
+// +kubebuilder:validation:Enum=None;KubernetesService;Static
+type BackendEndpointHostnameType string
+
+const (
+ // BackendEndpointHostnameTypeNone does not attach hostnames to backend endpoints.
+ BackendEndpointHostnameTypeNone BackendEndpointHostnameType = "None"
+ // BackendEndpointHostnameTypeKubernetesService uses the Kubernetes Service FQDN.
+ BackendEndpointHostnameTypeKubernetesService BackendEndpointHostnameType = "KubernetesService"
+ // BackendEndpointHostnameTypeStatic uses a user-specified static hostname.
+ BackendEndpointHostnameTypeStatic BackendEndpointHostnameType = "Static"
+)
+
+// BackendEndpointHostname configures hostnames attached to backend endpoints.
+//
+// +kubebuilder:validation:XValidation:message="hostname must be set when type is Static",rule="self.type == 'Static' ? has(self.hostname) : true"
+// +kubebuilder:validation:XValidation:message="hostname must not be set when type is not Static",rule="self.type != 'Static' ? !has(self.hostname) : true"
+type BackendEndpointHostname struct {
+ // Type determines how endpoint hostnames should be populated.
+ //
+ // +kubebuilder:validation:Required
+ Type BackendEndpointHostnameType `json:"type"`
+
+ // Hostname is a custom static hostname to attach to backend endpoints.
+ // This field is required when type is "Static" and must not be set for other types.
+ //
+ // +optional
+ // +kubebuilder:validation:MinLength=1
+ // +kubebuilder:validation:MaxLength=253
+ // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
+ Hostname *string `json:"hostname,omitempty"`
+}
+
// CIDR defines a CIDR Address range.
// A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64".
// +kubebuilder:validation:Pattern=`((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+))`
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index ed5a44424d..b9ecab19e6 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -510,6 +510,26 @@ func (in *BackendEndpoint) DeepCopy() *BackendEndpoint {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BackendEndpointHostname) DeepCopyInto(out *BackendEndpointHostname) {
+ *out = *in
+ if in.Hostname != nil {
+ in, out := &in.Hostname, &out.Hostname
+ *out = new(string)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendEndpointHostname.
+func (in *BackendEndpointHostname) DeepCopy() *BackendEndpointHostname {
+ if in == nil {
+ return nil
+ }
+ out := new(BackendEndpointHostname)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackendList) DeepCopyInto(out *BackendList) {
*out = *in
@@ -896,6 +916,11 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec)
*out = new(RoutingType)
**out = **in
}
+ if in.EndpointHostname != nil {
+ in, out := &in.EndpointHostname, &out.EndpointHostname
+ *out = new(BackendEndpointHostname)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendTrafficPolicySpec.
diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 72e4b69d56..b32a55696e 100644
--- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -540,6 +540,35 @@ spec:
Defaults to true.
type: boolean
type: object
+ endpointHostname:
+ description: |-
+ EndpointHostname configures the hostname value attached to backend endpoints.
+ If unset, no hostname is attached to Kubernetes Service endpoints.
+ properties:
+ hostname:
+ description: |-
+ Hostname is a custom static hostname to attach to backend endpoints.
+ This field is required when type is "Static" and must not be set for other types.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ type:
+ description: Type determines how endpoint hostnames should be
+ populated.
+ enum:
+ - None
+ - KubernetesService
+ - Static
+ type: string
+ required:
+ - type
+ type: object
+ x-kubernetes-validations:
+ - message: hostname must be set when type is Static
+ rule: 'self.type == ''Static'' ? has(self.hostname) : true'
+ - message: hostname must not be set when type is not Static
+ rule: 'self.type != ''Static'' ? !has(self.hostname) : true'
faultInjection:
description: |-
FaultInjection defines the fault injection policy to be applied. This configuration can be used to
diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index a79ec46e60..3425cbae3b 100644
--- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -539,6 +539,35 @@ spec:
Defaults to true.
type: boolean
type: object
+ endpointHostname:
+ description: |-
+ EndpointHostname configures the hostname value attached to backend endpoints.
+ If unset, no hostname is attached to Kubernetes Service endpoints.
+ properties:
+ hostname:
+ description: |-
+ Hostname is a custom static hostname to attach to backend endpoints.
+ This field is required when type is "Static" and must not be set for other types.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ type:
+ description: Type determines how endpoint hostnames should be
+ populated.
+ enum:
+ - None
+ - KubernetesService
+ - Static
+ type: string
+ required:
+ - type
+ type: object
+ x-kubernetes-validations:
+ - message: hostname must be set when type is Static
+ rule: 'self.type == ''Static'' ? has(self.hostname) : true'
+ - message: hostname must not be set when type is not Static
+ rule: 'self.type != ''Static'' ? !has(self.hostname) : true'
faultInjection:
description: |-
FaultInjection defines the fault injection policy to be applied. This configuration can be used to
diff --git a/internal/cmd/egctl/translate.go b/internal/cmd/egctl/translate.go
index 62d6e0f5ee..d18811aa7c 100644
--- a/internal/cmd/egctl/translate.go
+++ b/internal/cmd/egctl/translate.go
@@ -25,6 +25,7 @@ import (
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/api/v1alpha1/validation"
+ "github.com/envoyproxy/gateway/internal/envoygateway/config"
"github.com/envoyproxy/gateway/internal/gatewayapi"
"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
"github.com/envoyproxy/gateway/internal/gatewayapi/status"
@@ -292,6 +293,7 @@ func translateGatewayAPIToIR(resources *resource.Resources) (*gatewayapi.Transla
EndpointRoutingDisabled: true,
EnvoyPatchPolicyEnabled: true,
BackendEnabled: true,
+ DNSDomain: config.DefaultDNSDomain,
// Discard logs during translation for egctl command to avoid polluting output
Logger: logging.DefaultLogger(io.Discard, egv1a1.LogLevelInfo),
}
@@ -322,6 +324,7 @@ func translateGatewayAPIToGatewayAPI(resources *resource.Resources) (resource.Re
EndpointRoutingDisabled: true,
EnvoyPatchPolicyEnabled: true,
BackendEnabled: true,
+ DNSDomain: config.DefaultDNSDomain,
Logger: logging.DefaultLogger(io.Discard, egv1a1.LogLevelInfo),
}
gRes, _ := gTranslator.Translate(resources)
@@ -362,6 +365,7 @@ func TranslateGatewayAPIToXds(namespace, dnsDomain, resourceType string, resourc
EndpointRoutingDisabled: opts.EndpointRoutingDisabled,
EnvoyPatchPolicyEnabled: opts.EnvoyPatchPolicyEnabled,
BackendEnabled: opts.BackendEnabled,
+ DNSDomain: dnsDomain,
Logger: logging.DefaultLogger(io.Discard, egv1a1.LogLevelInfo),
}
gRes, _ := gTranslator.Translate(resources)
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 049cd15b7b..72a2d0d122 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -53,6 +53,15 @@ type BTPRoutingTypeIndex struct {
gatewayLevel map[btpRoutingKey]*egv1a1.RoutingType
}
+// BTPEndpointHostnameIndex holds EndpointHostname values from BackendTrafficPolicies.
+// This avoids an O(BTPs) lookup for every iteration of processDestination.
+type BTPEndpointHostnameIndex struct {
+ routeRuleLevel map[btpRoutingKey]*egv1a1.BackendEndpointHostname
+ routeLevel map[btpRoutingKey]*egv1a1.BackendEndpointHostname
+ listenerLevel map[btpRoutingKey]*egv1a1.BackendEndpointHostname
+ gatewayLevel map[btpRoutingKey]*egv1a1.BackendEndpointHostname
+}
+
// BuildBTPRoutingTypeIndex builds a pre-computed index of RoutingType values
// from BackendTrafficPolicies, organized by priority-level.
// BTPs are pre-sorted by the provider layer, so first-write-wins respects priority.
@@ -66,6 +75,16 @@ func hasBTPRoutingType(btps []*egv1a1.BackendTrafficPolicy) bool {
return false
}
+func hasBTPEndpointHostname(btps []*egv1a1.BackendTrafficPolicy) bool {
+ for _, btp := range btps {
+ if btp.Spec.EndpointHostname != nil {
+ return true
+ }
+ }
+
+ return false
+}
+
func BuildBTPRoutingTypeIndex(
btps []*egv1a1.BackendTrafficPolicy,
routes []client.Object,
@@ -136,6 +155,66 @@ func BuildBTPRoutingTypeIndex(
return idx
}
+func BuildBTPEndpointHostnameIndex(
+ btps []*egv1a1.BackendTrafficPolicy,
+ routes []client.Object,
+ gateways []*GatewayContext,
+) *BTPEndpointHostnameIndex {
+ idx := &BTPEndpointHostnameIndex{
+ routeRuleLevel: make(map[btpRoutingKey]*egv1a1.BackendEndpointHostname),
+ routeLevel: make(map[btpRoutingKey]*egv1a1.BackendEndpointHostname),
+ listenerLevel: make(map[btpRoutingKey]*egv1a1.BackendEndpointHostname),
+ gatewayLevel: make(map[btpRoutingKey]*egv1a1.BackendEndpointHostname),
+ }
+
+ allTargets := make([]client.Object, 0, len(routes)+len(gateways))
+ allTargets = append(allTargets, routes...)
+ for _, gw := range gateways {
+ allTargets = append(allTargets, gw)
+ }
+
+ for _, btp := range btps {
+ if btp.Spec.EndpointHostname == nil {
+ continue
+ }
+
+ refs := getPolicyTargetRefs(btp.Spec.PolicyTargetReferences, allTargets, btp.Namespace)
+ for _, ref := range refs {
+ kind := string(ref.Kind)
+ key := btpRoutingKey{
+ Kind: kind,
+ Namespace: btp.Namespace,
+ Name: string(ref.Name),
+ SectionName: string(ptr.Deref(ref.SectionName, "")),
+ }
+
+ if kind == resource.KindGateway {
+ if ref.SectionName != nil {
+ if _, exists := idx.listenerLevel[key]; !exists {
+ idx.listenerLevel[key] = btp.Spec.EndpointHostname
+ }
+ } else {
+ if _, exists := idx.gatewayLevel[key]; !exists {
+ idx.gatewayLevel[key] = btp.Spec.EndpointHostname
+ }
+ }
+ } else {
+ if ref.SectionName != nil {
+ if _, exists := idx.routeRuleLevel[key]; !exists {
+ idx.routeRuleLevel[key] = btp.Spec.EndpointHostname
+ }
+ } else {
+ if _, exists := idx.routeLevel[key]; !exists {
+ idx.routeLevel[key] = btp.Spec.EndpointHostname
+ }
+ }
+ }
+ }
+ }
+
+ return idx
+}
+
// LookupBTPRoutingType resolves the RoutingType for a specific route rule
// and gateway/listener combination by checking the index in
// priority order: routeRule > route > listener > gateway.
@@ -200,6 +279,65 @@ func (idx *BTPRoutingTypeIndex) LookupBTPRoutingType(
return nil
}
+// LookupBTPEndpointHostname resolves the EndpointHostname for a specific route rule
+// and gateway/listener combination by checking the index in
+// priority order: routeRule > route > listener > gateway.
+func (idx *BTPEndpointHostnameIndex) LookupBTPEndpointHostname(
+ routeKind gwapiv1.Kind,
+ routeNN types.NamespacedName,
+ gatewayNN types.NamespacedName,
+ listenerName *gwapiv1.SectionName,
+ routeRuleName *gwapiv1.SectionName,
+) *egv1a1.BackendEndpointHostname {
+ if idx == nil {
+ return nil
+ }
+
+ if routeRuleName != nil {
+ key := btpRoutingKey{
+ Kind: string(routeKind),
+ Namespace: routeNN.Namespace,
+ Name: routeNN.Name,
+ SectionName: string(*routeRuleName),
+ }
+ if eh, ok := idx.routeRuleLevel[key]; ok {
+ return eh
+ }
+ }
+
+ routeKey := btpRoutingKey{
+ Kind: string(routeKind),
+ Namespace: routeNN.Namespace,
+ Name: routeNN.Name,
+ }
+ if eh, ok := idx.routeLevel[routeKey]; ok {
+ return eh
+ }
+
+ if listenerName != nil {
+ listenerKey := btpRoutingKey{
+ Kind: resource.KindGateway,
+ Namespace: gatewayNN.Namespace,
+ Name: gatewayNN.Name,
+ SectionName: string(*listenerName),
+ }
+ if eh, ok := idx.listenerLevel[listenerKey]; ok {
+ return eh
+ }
+ }
+
+ gwKey := btpRoutingKey{
+ Kind: resource.KindGateway,
+ Namespace: gatewayNN.Namespace,
+ Name: gatewayNN.Name,
+ }
+ if eh, ok := idx.gatewayLevel[gwKey]; ok {
+ return eh
+ }
+
+ return nil
+}
+
// deprecatedFieldsUsedInBackendTrafficPolicy returns a map of deprecated field paths to their alternatives.
func deprecatedFieldsUsedInBackendTrafficPolicy(policy *egv1a1.BackendTrafficPolicy) map[string]string {
deprecatedFields := make(map[string]string)
diff --git a/internal/gatewayapi/backendtrafficpolicy_test.go b/internal/gatewayapi/backendtrafficpolicy_test.go
index 56c0cd9bfd..f6a4fe3683 100644
--- a/internal/gatewayapi/backendtrafficpolicy_test.go
+++ b/internal/gatewayapi/backendtrafficpolicy_test.go
@@ -1818,3 +1818,163 @@ func TestBTPRoutingTypeIndex(t *testing.T) {
})
}
}
+
+func TestBTPEndpointHostnameIndex(t *testing.T) {
+ kubernetesService := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ }
+ none := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeNone,
+ }
+
+ defaultHTTPRoute := &gwapiv1.HTTPRoute{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "default",
+ Name: "route-1",
+ },
+ }
+ defaultGateway := &GatewayContext{
+ Gateway: &gwapiv1.Gateway{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "default",
+ Name: "gateway-1",
+ },
+ },
+ }
+
+ routeNN := types.NamespacedName{Namespace: "default", Name: "route-1"}
+ gatewayNN := types.NamespacedName{Namespace: "default", Name: "gateway-1"}
+
+ tests := []struct {
+ name string
+ btps []*egv1a1.BackendTrafficPolicy
+ listenerName *gwapiv1.SectionName
+ routeRuleName *gwapiv1.SectionName
+ expected *egv1a1.BackendEndpointHostname
+ }{
+ {
+ name: "no BTPs",
+ expected: nil,
+ },
+ {
+ name: "route has priority over gateway",
+ btps: []*egv1a1.BackendTrafficPolicy{
+ {
+ ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "btp-gateway"},
+ Spec: egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+ Group: gwapiv1.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1.Kind("Gateway"),
+ Name: gwapiv1.ObjectName("gateway-1"),
+ },
+ },
+ },
+ EndpointHostname: none,
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "btp-route"},
+ Spec: egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+ Group: gwapiv1.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1.Kind("HTTPRoute"),
+ Name: gwapiv1.ObjectName("route-1"),
+ },
+ },
+ },
+ EndpointHostname: kubernetesService,
+ },
+ },
+ },
+ expected: kubernetesService,
+ },
+ {
+ name: "route rule has priority over route",
+ btps: []*egv1a1.BackendTrafficPolicy{
+ {
+ ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "btp-route"},
+ Spec: egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+ Group: gwapiv1.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1.Kind("HTTPRoute"),
+ Name: gwapiv1.ObjectName("route-1"),
+ },
+ },
+ },
+ EndpointHostname: kubernetesService,
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "btp-rule"},
+ Spec: egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+ Group: gwapiv1.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1.Kind("HTTPRoute"),
+ Name: gwapiv1.ObjectName("route-1"),
+ },
+ SectionName: new(gwapiv1.SectionName("rule-0")),
+ },
+ },
+ EndpointHostname: none,
+ },
+ },
+ },
+ routeRuleName: new(gwapiv1.SectionName("rule-0")),
+ expected: none,
+ },
+ {
+ name: "listener has priority over gateway",
+ btps: []*egv1a1.BackendTrafficPolicy{
+ {
+ ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "btp-gateway"},
+ Spec: egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+ Group: gwapiv1.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1.Kind("Gateway"),
+ Name: gwapiv1.ObjectName("gateway-1"),
+ },
+ },
+ },
+ EndpointHostname: none,
+ },
+ },
+ {
+ ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "btp-listener"},
+ Spec: egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
+ Group: gwapiv1.Group("gateway.networking.k8s.io"),
+ Kind: gwapiv1.Kind("Gateway"),
+ Name: gwapiv1.ObjectName("gateway-1"),
+ },
+ SectionName: new(gwapiv1.SectionName("http")),
+ },
+ },
+ EndpointHostname: kubernetesService,
+ },
+ },
+ },
+ listenerName: new(gwapiv1.SectionName("http")),
+ expected: kubernetesService,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ idx := BuildBTPEndpointHostnameIndex(tt.btps, []client.Object{defaultHTTPRoute}, []*GatewayContext{defaultGateway})
+ got := idx.LookupBTPEndpointHostname("HTTPRoute", routeNN, gatewayNN, tt.listenerName, tt.routeRuleName)
+ require.Equal(t, tt.expected, got)
+ })
+ }
+}
diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go
index 923966465b..62d4afcb33 100644
--- a/internal/gatewayapi/contexts.go
+++ b/internal/gatewayapi/contexts.go
@@ -863,15 +863,16 @@ type backendServiceKey struct {
}
type TranslatorContext struct {
- NamespaceMap map[types.NamespacedName]*corev1.Namespace
- ServiceMap map[types.NamespacedName]*corev1.Service
- ServiceImportMap map[types.NamespacedName]*mcsapiv1a1.ServiceImport
- BackendMap map[types.NamespacedName]*egv1a1.Backend
- SecretMap map[types.NamespacedName]*corev1.Secret
- ConfigMapMap map[types.NamespacedName]*corev1.ConfigMap
- ClusterTrustBundleMap map[types.NamespacedName]*certificatesv1b1.ClusterTrustBundle
- EndpointSliceMap map[backendServiceKey][]*discoveryv1.EndpointSlice
- BTPRoutingTypeIndex *BTPRoutingTypeIndex
+ NamespaceMap map[types.NamespacedName]*corev1.Namespace
+ ServiceMap map[types.NamespacedName]*corev1.Service
+ ServiceImportMap map[types.NamespacedName]*mcsapiv1a1.ServiceImport
+ BackendMap map[types.NamespacedName]*egv1a1.Backend
+ SecretMap map[types.NamespacedName]*corev1.Secret
+ ConfigMapMap map[types.NamespacedName]*corev1.ConfigMap
+ ClusterTrustBundleMap map[types.NamespacedName]*certificatesv1b1.ClusterTrustBundle
+ EndpointSliceMap map[backendServiceKey][]*discoveryv1.EndpointSlice
+ BTPRoutingTypeIndex *BTPRoutingTypeIndex
+ BTPEndpointHostnameIndex *BTPEndpointHostnameIndex
}
func (t *TranslatorContext) GetNamespace(name string) *corev1.Namespace {
diff --git a/internal/gatewayapi/ext_service.go b/internal/gatewayapi/ext_service.go
index 9a5f506051..01fef32f2e 100644
--- a/internal/gatewayapi/ext_service.go
+++ b/internal/gatewayapi/ext_service.go
@@ -110,7 +110,7 @@ func (t *Translator) processExtServiceDestination(
switch KindDerefOr(backendRef.Kind, resource.KindService) {
case resource.KindService:
- ds, err = t.processServiceDestinationSetting(settingName, backendRef.BackendObjectReference, backendNamespace, protocol, gtwCtx.envoyProxy, nil)
+ ds, err = t.processServiceDestinationSetting(settingName, backendRef.BackendObjectReference, backendNamespace, protocol, gtwCtx.envoyProxy, nil, nil)
if err != nil {
return nil, err
}
diff --git a/internal/gatewayapi/globalresources.go b/internal/gatewayapi/globalresources.go
index 1000890724..3c6f31ae10 100644
--- a/internal/gatewayapi/globalresources.go
+++ b/internal/gatewayapi/globalresources.go
@@ -85,7 +85,7 @@ func (t *Translator) processServiceClusterForGateway(gateway *GatewayContext, re
Namespace: NamespacePtr(svcCluster.Namespace),
Port: PortNumPtr(svcCluster.Spec.Ports[0].Port),
}
- dst, err := t.processServiceDestinationSetting(irKey, bRef, svcCluster.Namespace, ir.AppProtocol(svcCluster.Spec.Ports[0].Protocol), resources.EnvoyProxyForGatewayClass, nil)
+ dst, err := t.processServiceDestinationSetting(irKey, bRef, svcCluster.Namespace, ir.AppProtocol(svcCluster.Spec.Ports[0].Protocol), resources.EnvoyProxyForGatewayClass, nil, nil)
if err != nil {
return "", nil
}
diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go
index c088de59b8..7a8a22e2a5 100644
--- a/internal/gatewayapi/listener.go
+++ b/internal/gatewayapi/listener.go
@@ -1208,7 +1208,7 @@ func (t *Translator) processBackendRefsForTelemetry(name string, backendCluster
if err := t.validateBackendRefService(ref.BackendObjectReference, ns, corev1.ProtocolTCP); err != nil {
return nil, nil, err
}
- ds, err = t.processServiceDestinationSetting(name, ref.BackendObjectReference, ns, ir.TCP, envoyProxy, nil)
+ ds, err = t.processServiceDestinationSetting(name, ref.BackendObjectReference, ns, ir.TCP, envoyProxy, nil, nil)
if err != nil {
return nil, nil, err
}
diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go
index 0812193617..e932a12c00 100644
--- a/internal/gatewayapi/route.go
+++ b/internal/gatewayapi/route.go
@@ -24,6 +24,7 @@ import (
mcsapiv1a1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+ "github.com/envoyproxy/gateway/internal/envoygateway/config"
"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
"github.com/envoyproxy/gateway/internal/gatewayapi/status"
"github.com/envoyproxy/gateway/internal/ir"
@@ -1893,6 +1894,16 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
routeRuleName,
)
}
+ var btpEndpointHostname *egv1a1.BackendEndpointHostname
+ if gatewayCtx != nil {
+ btpEndpointHostname = t.BTPEndpointHostnameIndex.LookupBTPEndpointHostname(
+ route.GetRouteType(),
+ types.NamespacedName{Namespace: route.GetNamespace(), Name: route.GetName()},
+ types.NamespacedName{Namespace: gatewayCtx.GetNamespace(), Name: gatewayCtx.GetName()},
+ parentRef.SectionName,
+ routeRuleName,
+ )
+ }
protocol := inspectAppProtocolByRouteKind(routeType)
@@ -1922,7 +1933,7 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
return emptyDS, nil, err
}
case resource.KindService:
- ds, err = t.processServiceDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, envoyProxy, btpRoutingType)
+ ds, err = t.processServiceDestinationSetting(name, backendRef.BackendObjectReference, backendNamespace, protocol, envoyProxy, btpRoutingType, btpEndpointHostname)
if err != nil {
return emptyDS, nil, err
}
@@ -1963,7 +1974,6 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe
if filtersErr != nil {
return emptyDS, nil, status.NewRouteStatusError(filtersErr, status.RouteReasonInvalidBackendFilters)
}
-
if err := validateDestinationSettings(ds, t.IsServiceRouting(envoyProxy, btpRoutingType), backendRef.Kind); err != nil {
return emptyDS, nil, err
}
@@ -1992,6 +2002,13 @@ func validateDestinationSettings(destinationSettings *ir.DestinationSetting, isS
return nil
}
+func (t *Translator) dnsDomain() string {
+ if t.DNSDomain != "" {
+ return t.DNSDomain
+ }
+ return config.DefaultDNSDomain
+}
+
// isServiceHeadless reports true when a Kubernetes Service is headless.
func isServiceHeadless(service *corev1.Service) bool {
if service == nil {
@@ -2047,7 +2064,7 @@ func (t *Translator) processServiceImportDestinationSetting(
useEndpointRouting := !t.IsServiceRouting(envoyProxy, btpRoutingType) || isHeadless
if useEndpointRouting {
endpointSlices := t.GetEndpointSlicesForBackend(backendNamespace, string(backendRef.Name), resource.KindServiceImport)
- endpoints, addrType = getIREndpointsFromEndpointSlices(endpointSlices, servicePort.Name, getServicePortProtocol(servicePort.Protocol))
+ endpoints, addrType = getIREndpointsFromEndpointSlices(endpointSlices, servicePort.Name, getServicePortProtocol(servicePort.Protocol), nil)
if len(endpoints) == 0 {
return nil, status.NewRouteStatusError(
fmt.Errorf("no ready endpoints for the related ServiceImport %s/%s", backendNamespace, backendRef.Name),
@@ -2079,6 +2096,7 @@ func (t *Translator) processServiceDestinationSetting(
defaultProtocol ir.AppProtocol,
envoyProxy *egv1a1.EnvoyProxy,
btpRoutingType *egv1a1.RoutingType,
+ btpEndpointHostname *egv1a1.BackendEndpointHostname,
) (*ir.DestinationSetting, status.Error) {
var (
endpoints []*ir.DestinationEndpoint
@@ -2117,9 +2135,10 @@ func (t *Translator) processServiceDestinationSetting(
// Route to endpoints by default, or if service routing is enabled but service is headless
useEndpointRouting := !t.IsServiceRouting(envoyProxy, btpRoutingType) || isHeadless
+ endpointHostname := t.serviceEndpointHostname(service, btpEndpointHostname)
if useEndpointRouting {
endpointSlices := t.GetEndpointSlicesForBackend(backendNamespace, string(backendRef.Name), KindDerefOr(backendRef.Kind, resource.KindService))
- endpoints, addrType = getIREndpointsFromEndpointSlices(endpointSlices, servicePort.Name, getServicePortProtocol(servicePort.Protocol))
+ endpoints, addrType = getIREndpointsFromEndpointSlices(endpointSlices, servicePort.Name, getServicePortProtocol(servicePort.Protocol), endpointHostname)
if len(endpoints) == 0 {
return nil, status.NewRouteStatusError(
fmt.Errorf("no ready endpoints for the related Service %s/%s", backendNamespace, backendRef.Name),
@@ -2128,7 +2147,7 @@ func (t *Translator) processServiceDestinationSetting(
}
} else {
// Use Service ClusterIP routing
- ep := ir.NewDestEndpoint(nil, service.Spec.ClusterIP, uint32(*backendRef.Port), false, nil)
+ ep := ir.NewDestEndpoint(endpointHostname, service.Spec.ClusterIP, uint32(*backendRef.Port), false, nil)
endpoints = append(endpoints, ep)
}
@@ -2143,6 +2162,27 @@ func (t *Translator) processServiceDestinationSetting(
}, nil
}
+func (t *Translator) serviceEndpointHostname(service *corev1.Service, endpointHostname *egv1a1.BackendEndpointHostname) *string {
+ if endpointHostname == nil {
+ return nil
+ }
+
+ switch endpointHostname.Type {
+ case egv1a1.BackendEndpointHostnameTypeKubernetesService:
+ if service == nil || service.Name == "" || service.Namespace == "" {
+ return nil
+ }
+ return new(fmt.Sprintf("%s.%s.svc.%s", service.Name, service.Namespace, t.dnsDomain()))
+ case egv1a1.BackendEndpointHostnameTypeStatic:
+ if endpointHostname.Hostname == nil || *endpointHostname.Hostname == "" {
+ return nil
+ }
+ return endpointHostname.Hostname
+ default:
+ return nil
+ }
+}
+
func getBackendFilters(routeType gwapiv1.Kind, backendRefContext BackendRefContext) (backendFilters any) {
filters := backendRefContext.GetFilters()
if filters == nil {
@@ -2332,7 +2372,7 @@ func (t *Translator) processAllowedListenersForParentRefs(
return relevantRoute
}
-func getIREndpointsFromEndpointSlices(endpointSlices []*discoveryv1.EndpointSlice, portName string, portProtocol corev1.Protocol) ([]*ir.DestinationEndpoint, *ir.DestinationAddressType) {
+func getIREndpointsFromEndpointSlices(endpointSlices []*discoveryv1.EndpointSlice, portName string, portProtocol corev1.Protocol, endpointHostname *string) ([]*ir.DestinationEndpoint, *ir.DestinationAddressType) {
var (
dstEndpoints []*ir.DestinationEndpoint
dstAddrType *ir.DestinationAddressType
@@ -2345,7 +2385,7 @@ func getIREndpointsFromEndpointSlices(endpointSlices []*discoveryv1.EndpointSlic
} else {
addrTypeMap[ir.IP]++
}
- endpoints := getIREndpointsFromEndpointSlice(endpointSlice, portName, portProtocol)
+ endpoints := getIREndpointsFromEndpointSlice(endpointSlice, portName, portProtocol, endpointHostname)
dstEndpoints = append(dstEndpoints, endpoints...)
}
@@ -2363,7 +2403,7 @@ func getIREndpointsFromEndpointSlices(endpointSlices []*discoveryv1.EndpointSlic
return dstEndpoints, dstAddrType
}
-func getIREndpointsFromEndpointSlice(endpointSlice *discoveryv1.EndpointSlice, portName string, portProtocol corev1.Protocol) []*ir.DestinationEndpoint {
+func getIREndpointsFromEndpointSlice(endpointSlice *discoveryv1.EndpointSlice, portName string, portProtocol corev1.Protocol, endpointHostname *string) []*ir.DestinationEndpoint {
var endpoints []*ir.DestinationEndpoint
for _, endpoint := range endpointSlice.Endpoints {
for _, endpointPort := range endpointSlice.Ports {
@@ -2385,7 +2425,7 @@ func getIREndpointsFromEndpointSlice(endpointSlice *discoveryv1.EndpointSlice, p
}
for _, address := range endpoint.Addresses {
- ep := ir.NewDestEndpoint(nil, address, uint32(*endpointPort.Port), draining, endpoint.Zone)
+ ep := ir.NewDestEndpoint(endpointHostname, address, uint32(*endpointPort.Port), draining, endpoint.Zone)
endpoints = append(endpoints, ep)
}
diff --git a/internal/gatewayapi/route_test.go b/internal/gatewayapi/route_test.go
index 3c86302f1e..3d3175030e 100644
--- a/internal/gatewayapi/route_test.go
+++ b/internal/gatewayapi/route_test.go
@@ -13,6 +13,7 @@ import (
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
@@ -295,7 +296,7 @@ func TestGetIREndpointsFromEndpointSlices(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- endpoints, addrType := getIREndpointsFromEndpointSlices(tt.endpointSlices, tt.portName, tt.portProtocol)
+ endpoints, addrType := getIREndpointsFromEndpointSlices(tt.endpointSlices, tt.portName, tt.portProtocol, nil)
fmt.Printf("Test case: %s\n", tt.name)
fmt.Printf("Number of endpoints: %d\n", len(endpoints))
@@ -533,3 +534,169 @@ func TestIsServiceHeadless(t *testing.T) {
})
}
}
+
+func TestServiceEndpointHostname(t *testing.T) {
+ t.Run("nil setting returns nil", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"}}
+
+ hostname := translator.serviceEndpointHostname(service, nil)
+
+ require.Nil(t, hostname)
+ })
+
+ t.Run("none type returns nil", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeNone,
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Nil(t, hostname)
+ })
+
+ t.Run("kubernetes service uses default cluster domain", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Equal(t, new("service-1.default.svc.cluster.local"), hostname)
+ })
+
+ t.Run("kubernetes service uses configured dns domain", func(t *testing.T) {
+ translator := &Translator{DNSDomain: "example.internal"}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Equal(t, new("service-1.default.svc.example.internal"), hostname)
+ })
+
+ t.Run("kubernetes service ignores missing service name", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "default"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Nil(t, hostname)
+ })
+
+ t.Run("kubernetes service ignores missing service namespace", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Nil(t, hostname)
+ })
+
+ t.Run("endpoint slices use resolved hostname", func(t *testing.T) {
+ endpointSlices := []*discoveryv1.EndpointSlice{{
+ AddressType: discoveryv1.AddressTypeIPv4,
+ Endpoints: []discoveryv1.Endpoint{{
+ Addresses: []string{"10.0.0.1"},
+ Conditions: discoveryv1.EndpointConditions{
+ Ready: new(true),
+ },
+ }},
+ Ports: []discoveryv1.EndpointPort{{
+ Name: new("http"),
+ Protocol: new(corev1.ProtocolTCP),
+ Port: new(int32(8080)),
+ }},
+ }}
+
+ endpoints, _ := getIREndpointsFromEndpointSlices(endpointSlices, "http", corev1.ProtocolTCP, new("service-1.default.svc.cluster.local"))
+
+ require.Len(t, endpoints, 1)
+ require.Equal(t, new("service-1.default.svc.cluster.local"), endpoints[0].Hostname)
+ })
+
+ t.Run("cluster ip endpoint uses resolved hostname", func(t *testing.T) {
+ translator := &Translator{}
+ port := int32(8080)
+ portNum := port
+ backendRef := gwapiv1.BackendObjectReference{
+ Name: "service-1",
+ Port: &portNum,
+ }
+ service := &corev1.Service{
+ ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"},
+ Spec: corev1.ServiceSpec{
+ ClusterIP: "10.0.0.1",
+ Ports: []corev1.ServicePort{{
+ Name: "http",
+ Port: port,
+ }},
+ },
+ }
+ translator.TranslatorContext = &TranslatorContext{
+ ServiceMap: map[types.NamespacedName]*corev1.Service{
+ {Namespace: "default", Name: "service-1"}: service,
+ },
+ }
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ }
+ serviceRouting := egv1a1.ServiceRoutingType
+
+ ds, err := translator.processServiceDestinationSetting("test", backendRef, "default", ir.HTTP, nil, &serviceRouting, setting)
+
+ require.NoError(t, err)
+ require.Len(t, ds.Endpoints, 1)
+ require.Equal(t, new("service-1.default.svc.cluster.local"), ds.Endpoints[0].Hostname)
+ })
+
+ t.Run("static type returns specified hostname", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeStatic,
+ Hostname: new("custom-static.example.com"),
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Equal(t, new("custom-static.example.com"), hostname)
+ })
+
+ t.Run("static type with nil hostname returns nil", func(t *testing.T) {
+ translator := &Translator{}
+ service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "service-1", Namespace: "default"}}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeStatic,
+ Hostname: nil,
+ }
+
+ hostname := translator.serviceEndpointHostname(service, setting)
+
+ require.Nil(t, hostname)
+ })
+
+ t.Run("static type ignores nil service", func(t *testing.T) {
+ translator := &Translator{}
+ setting := &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeStatic,
+ Hostname: new("custom-static.example.com"),
+ }
+
+ hostname := translator.serviceEndpointHostname(nil, setting)
+
+ require.Equal(t, new("custom-static.example.com"), hostname)
+ })
+}
diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go
index bfc3e39c48..e386321ca0 100644
--- a/internal/gatewayapi/runner/runner.go
+++ b/internal/gatewayapi/runner/runner.go
@@ -297,6 +297,7 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re
BackendEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableBackend,
SDSSecretRefEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableSDSSecretRef,
ControllerNamespace: r.ControllerNamespace,
+ DNSDomain: r.DNSDomain,
GatewayNamespaceMode: r.EnvoyGateway.GatewayNamespaceMode(),
MergeGateways: gatewayapi.IsMergeGatewaysEnabled(resources),
WasmCache: r.wasmCache,
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml
index 09b7115694..b953dbff7b 100644
--- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml
@@ -181,3 +181,16 @@ httpFilters:
urlRewrite:
hostname:
type: Backend
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: BackendTrafficPolicy
+ metadata:
+ name: endpoint-hostname
+ namespace: default
+ spec:
+ targetRefs:
+ - group: gateway.networking.k8s.io
+ kind: HTTPRoute
+ name: httproute-2
+ endpointHostname:
+ type: KubernetesService
diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml
index 0dca8c386c..a6e5b684a6 100644
--- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml
+++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml
@@ -1,3 +1,31 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+ kind: BackendTrafficPolicy
+ metadata:
+ name: endpoint-hostname
+ namespace: default
+ spec:
+ endpointHostname:
+ type: KubernetesService
+ targetRefs:
+ - group: gateway.networking.k8s.io
+ kind: HTTPRoute
+ name: httproute-2
+ status:
+ ancestors:
+ - ancestorRef:
+ group: gateway.networking.k8s.io
+ kind: Gateway
+ name: gateway-1
+ namespace: envoy-gateway
+ sectionName: http
+ conditions:
+ - lastTransitionTime: null
+ message: Policy has been accepted.
+ reason: Accepted
+ status: "True"
+ type: Accepted
+ controllerName: gateway.envoyproxy.io/gatewayclass-controller
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
@@ -438,6 +466,7 @@ xdsIR:
- addressType: IP
endpoints:
- host: 7.7.7.7
+ hostname: service-1.default.svc.cluster.local
port: 8080
metadata:
kind: Service
@@ -453,11 +482,16 @@ xdsIR:
kind: HTTPRoute
name: httproute-2
namespace: default
+ policies:
+ - kind: BackendTrafficPolicy
+ name: endpoint-hostname
+ namespace: default
name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io
pathMatch:
distinct: false
name: ""
prefix: /valid-backend
+ traffic: {}
urlRewrite:
host:
backend: true
diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go
index b713dd1839..84f01ef816 100644
--- a/internal/gatewayapi/translator.go
+++ b/internal/gatewayapi/translator.go
@@ -113,6 +113,9 @@ type Translator struct {
// ControllerNamespace is the namespace that Envoy Gateway controller runs in.
ControllerNamespace string
+ // DNSDomain is the DNS domain used by Kubernetes services.
+ DNSDomain string
+
// WasmCache is the cache for Wasm modules.
WasmCache wasm.Cache
@@ -285,6 +288,14 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult,
t.GetNamespace,
)
}
+ t.BTPEndpointHostnameIndex = nil
+ if hasBTPEndpointHostname(resources.BackendTrafficPolicies) {
+ t.BTPEndpointHostnameIndex = BuildBTPEndpointHostnameIndex(
+ resources.BackendTrafficPolicies,
+ routesToObjects(resources),
+ acceptedGateways,
+ )
+ }
// Process ListenerSets and attach them to the relevant Gateways
t.ProcessListenerSets(resources.ListenerSets, acceptedGateways)
diff --git a/release-notes/current/new_features/8606-endpointhostname-btp.md b/release-notes/current/new_features/8606-endpointhostname-btp.md
new file mode 100644
index 0000000000..880781c5f4
--- /dev/null
+++ b/release-notes/current/new_features/8606-endpointhostname-btp.md
@@ -0,0 +1 @@
+Added EndpointHostname in BTP to set hostname field in Endpoint
\ No newline at end of file
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 741ff2f568..42554cfa2d 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -411,6 +411,37 @@ _Appears in:_
| `zone` | _string_ | false | | Zone defines the service zone of the backend endpoint. |
+#### BackendEndpointHostname
+
+
+
+BackendEndpointHostname configures hostnames attached to backend endpoints.
+
+_Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
+
+| Field | Type | Required | Default | Description |
+| --- | --- | --- | --- | --- |
+| `type` | _[BackendEndpointHostnameType](#backendendpointhostnametype)_ | true | | Type determines how endpoint hostnames should be populated. |
+| `hostname` | _string_ | false | | Hostname is a custom static hostname to attach to backend endpoints.
This field is required when type is "Static" and must not be set for other types. |
+
+
+#### BackendEndpointHostnameType
+
+_Underlying type:_ _string_
+
+BackendEndpointHostnameType defines how endpoint hostnames should be populated.
+
+_Appears in:_
+- [BackendEndpointHostname](#backendendpointhostname)
+
+| Value | Description |
+| ----- | ----------- |
+| `None` | BackendEndpointHostnameTypeNone does not attach hostnames to backend endpoints.
|
+| `KubernetesService` | BackendEndpointHostnameTypeKubernetesService uses the Kubernetes Service FQDN.
|
+| `Static` | BackendEndpointHostnameTypeStatic uses a user-specified static hostname.
|
+
+
#### BackendMetrics
@@ -596,6 +627,7 @@ _Appears in:_
| `requestBuffer` | _[RequestBuffer](#requestbuffer)_ | false | | RequestBuffer allows the gateway to buffer and fully receive each request from a client before continuing to send the request
upstream to the backends. This can be helpful to shield your backend servers from slow clients, and also to enforce a maximum size per request
as any requests larger than the buffer size will be rejected.
This can have a negative performance impact so should only be enabled when necessary.
When enabling this option, you should also configure your connection buffer size to account for these request buffers. There will also be an
increase in memory usage for Envoy that should be accounted for in your deployment settings.
Request buffering is incompatible with streaming APIs and protocol upgrades such as gRPC streaming and WebSocket. Do not enable this option
on routes that need those protocols, because requests can hang instead of being forwarded upstream. |
| `telemetry` | _[BackendTelemetry](#backendtelemetry)_ | false | | Telemetry configures the telemetry settings for the policy target (Gateway or xRoute).
This will override the telemetry settings in the EnvoyProxy resource. |
| `routingType` | _[RoutingType](#routingtype)_ | false | | RoutingType can be set to "Service" to use the Service Cluster IP for routing to the backend,
or it can be set to "Endpoint" to use Endpoint routing.
When specified, this overrides the EnvoyProxy-level setting for the relevant targetRefs.
If not specified, the EnvoyProxy-level setting is used. |
+| `endpointHostname` | _[BackendEndpointHostname](#backendendpointhostname)_ | false | | EndpointHostname configures the hostname value attached to backend endpoints.
If unset, no hostname is attached to Kubernetes Service endpoints. |
#### BackendType
diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go
index 6bd00eb1c0..52a9e826e6 100644
--- a/test/cel-validation/backendtrafficpolicy_test.go
+++ b/test/cel-validation/backendtrafficpolicy_test.go
@@ -3398,6 +3398,101 @@ func TestBackendTrafficPolicyTarget(t *testing.T) {
},
wantErrors: []string{},
},
+ {
+ desc: "valid endpoint hostname none",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "eg"},
+ },
+ },
+ EndpointHostname: &egv1a1.BackendEndpointHostname{Type: egv1a1.BackendEndpointHostnameTypeNone},
+ }
+ },
+ wantErrors: []string{},
+ },
+ {
+ desc: "valid endpoint hostname kubernetes service",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "eg"},
+ },
+ },
+ EndpointHostname: &egv1a1.BackendEndpointHostname{Type: egv1a1.BackendEndpointHostnameTypeKubernetesService},
+ }
+ },
+ wantErrors: []string{},
+ },
+ {
+ desc: "valid endpoint hostname static with hostname",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "eg"},
+ },
+ },
+ EndpointHostname: &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeStatic,
+ Hostname: new("custom-static.example.com"),
+ },
+ }
+ },
+ wantErrors: []string{},
+ },
+ {
+ desc: "invalid endpoint hostname static without hostname",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "eg"},
+ },
+ },
+ EndpointHostname: &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeStatic,
+ },
+ }
+ },
+ wantErrors: []string{"hostname must be set when type is Static"},
+ },
+ {
+ desc: "invalid endpoint hostname none with hostname",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "eg"},
+ },
+ },
+ EndpointHostname: &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeNone,
+ Hostname: new("custom-static.example.com"),
+ },
+ }
+ },
+ wantErrors: []string{"hostname must not be set when type is not Static"},
+ },
+ {
+ desc: "invalid endpoint hostname kubernetes service with hostname",
+ mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+ btp.Spec = egv1a1.BackendTrafficPolicySpec{
+ PolicyTargetReferences: egv1a1.PolicyTargetReferences{
+ TargetRef: &gwapiv1.LocalPolicyTargetReferenceWithSectionName{
+ LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{Group: "gateway.networking.k8s.io", Kind: "Gateway", Name: "eg"},
+ },
+ },
+ EndpointHostname: &egv1a1.BackendEndpointHostname{
+ Type: egv1a1.BackendEndpointHostnameTypeKubernetesService,
+ Hostname: new("custom-static.example.com"),
+ },
+ }
+ },
+ wantErrors: []string{"hostname must not be set when type is not Static"},
+ },
{
desc: "valid bandwidthLimit with request and response set to different limits",
mutate: func(btp *egv1a1.BackendTrafficPolicy) {
diff --git a/test/e2e/testdata/httproute-backend-endpoint-hostname.yaml b/test/e2e/testdata/httproute-backend-endpoint-hostname.yaml
new file mode 100644
index 0000000000..0cc81cedd1
--- /dev/null
+++ b/test/e2e/testdata/httproute-backend-endpoint-hostname.yaml
@@ -0,0 +1,105 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: backend-endpoint-hostname-with-btp
+ namespace: gateway-conformance-infra
+spec:
+ parentRefs:
+ - name: same-namespace
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /backend-endpoint-hostname-with-btp
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: gateway.envoyproxy.io
+ kind: HTTPRouteFilter
+ name: backend-endpoint-hostname-rewrite
+ backendRefs:
+ - name: infra-backend-v1
+ port: 8080
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: backend-endpoint-hostname-without-btp
+ namespace: gateway-conformance-infra
+spec:
+ parentRefs:
+ - name: same-namespace
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /backend-endpoint-hostname-without-btp
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: gateway.envoyproxy.io
+ kind: HTTPRouteFilter
+ name: backend-endpoint-hostname-rewrite
+ backendRefs:
+ - name: infra-backend-v1
+ port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: HTTPRouteFilter
+metadata:
+ name: backend-endpoint-hostname-rewrite
+ namespace: gateway-conformance-infra
+spec:
+ urlRewrite:
+ hostname:
+ type: Backend
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+ name: backend-endpoint-hostname
+ namespace: gateway-conformance-infra
+spec:
+ targetRefs:
+ - group: gateway.networking.k8s.io
+ kind: HTTPRoute
+ name: backend-endpoint-hostname-with-btp
+ endpointHostname:
+ type: KubernetesService
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: backend-endpoint-hostname-static
+ namespace: gateway-conformance-infra
+spec:
+ parentRefs:
+ - name: same-namespace
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /backend-endpoint-hostname-static
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: gateway.envoyproxy.io
+ kind: HTTPRouteFilter
+ name: backend-endpoint-hostname-rewrite
+ backendRefs:
+ - name: infra-backend-v1
+ port: 8080
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: BackendTrafficPolicy
+metadata:
+ name: backend-endpoint-hostname-static
+ namespace: gateway-conformance-infra
+spec:
+ targetRefs:
+ - group: gateway.networking.k8s.io
+ kind: HTTPRoute
+ name: backend-endpoint-hostname-static
+ endpointHostname:
+ type: Static
+ hostname: custom-static.example.com
diff --git a/test/e2e/testdata/httproute-rewrite-host.yaml b/test/e2e/testdata/httproute-rewrite-host.yaml
index 871b2008b3..29660d8462 100644
--- a/test/e2e/testdata/httproute-rewrite-host.yaml
+++ b/test/e2e/testdata/httproute-rewrite-host.yaml
@@ -5,35 +5,35 @@ metadata:
namespace: gateway-conformance-infra
spec:
parentRefs:
- - name: same-namespace
+ - name: same-namespace
rules:
- - matches:
- - path:
- type: PathPrefix
- value: /header
- filters:
- - type: ExtensionRef
- extensionRef:
- group: gateway.envoyproxy.io
- kind: HTTPRouteFilter
- name: header-host-rewrite
- backendRefs:
- - name: infra-backend-v1
- port: 8080
- - matches:
- - path:
- type: PathPrefix
- value: /backend
- filters:
- - type: ExtensionRef
- extensionRef:
- group: gateway.envoyproxy.io
- kind: HTTPRouteFilter
- name: backend-host-rewrite
- backendRefs:
- - group: gateway.envoyproxy.io
- kind: Backend
- name: backend-fqdn
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /header
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: gateway.envoyproxy.io
+ kind: HTTPRouteFilter
+ name: header-host-rewrite
+ backendRefs:
+ - name: infra-backend-v1
+ port: 8080
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /backend
+ filters:
+ - type: ExtensionRef
+ extensionRef:
+ group: gateway.envoyproxy.io
+ kind: HTTPRouteFilter
+ name: backend-host-rewrite
+ backendRefs:
+ - group: gateway.envoyproxy.io
+ kind: Backend
+ name: backend-fqdn
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: HTTPRouteFilter
@@ -63,6 +63,6 @@ metadata:
namespace: gateway-conformance-infra
spec:
endpoints:
- - fqdn:
- hostname: infra-backend-v1.gateway-conformance-infra.svc.cluster.local
- port: 8080
+ - fqdn:
+ hostname: infra-backend-v1.gateway-conformance-infra.svc.cluster.local
+ port: 8080
diff --git a/test/e2e/tests/httproute_backend_endpoint_hostname.go b/test/e2e/tests/httproute_backend_endpoint_hostname.go
new file mode 100644
index 0000000000..9f5f2e4118
--- /dev/null
+++ b/test/e2e/tests/httproute_backend_endpoint_hostname.go
@@ -0,0 +1,91 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build e2e
+
+package tests
+
+import (
+ "testing"
+
+ "k8s.io/apimachinery/pkg/types"
+ gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+ "sigs.k8s.io/gateway-api/conformance/utils/http"
+ "sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
+ "sigs.k8s.io/gateway-api/conformance/utils/suite"
+)
+
+func init() {
+ ConformanceTests = append(ConformanceTests, HTTPRouteBackendEndpointHostname)
+}
+
+var HTTPRouteBackendEndpointHostname = suite.ConformanceTest{
+ ShortName: "HTTPRouteBackendEndpointHostname",
+ Description: "An HTTPRoute with backend host rewrite uses BackendTrafficPolicy endpoint hostnames for Service backends",
+ Manifests: []string{"testdata/httproute-backend-endpoint-hostname.yaml"},
+ Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+ ns := "gateway-conformance-infra"
+ gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
+ withBTPRouteNN := types.NamespacedName{Name: "backend-endpoint-hostname-with-btp", Namespace: ns}
+ withoutBTPRouteNN := types.NamespacedName{Name: "backend-endpoint-hostname-without-btp", Namespace: ns}
+ staticBTPRouteNN := types.NamespacedName{Name: "backend-endpoint-hostname-static", Namespace: ns}
+ gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, withBTPRouteNN, withoutBTPRouteNN, staticBTPRouteNN)
+ kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, withBTPRouteNN, gwNN)
+ kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, withoutBTPRouteNN, gwNN)
+ kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, staticBTPRouteNN, gwNN)
+
+ testCases := []http.ExpectedResponse{
+ {
+ Request: http.Request{
+ Host: "example.com",
+ Path: "/backend-endpoint-hostname-with-btp",
+ },
+ ExpectedRequest: &http.ExpectedRequest{
+ Request: http.Request{
+ Path: "/backend-endpoint-hostname-with-btp",
+ Host: "infra-backend-v1.gateway-conformance-infra.svc.cluster.local",
+ },
+ },
+ Backend: "infra-backend-v1",
+ Namespace: ns,
+ },
+ {
+ Request: http.Request{
+ Host: "example.com",
+ Path: "/backend-endpoint-hostname-without-btp",
+ },
+ ExpectedRequest: &http.ExpectedRequest{
+ Request: http.Request{
+ Path: "/backend-endpoint-hostname-without-btp",
+ Host: "example.com",
+ },
+ },
+ Backend: "infra-backend-v1",
+ Namespace: ns,
+ },
+ {
+ Request: http.Request{
+ Host: "example.com",
+ Path: "/backend-endpoint-hostname-static",
+ },
+ ExpectedRequest: &http.ExpectedRequest{
+ Request: http.Request{
+ Path: "/backend-endpoint-hostname-static",
+ Host: "custom-static.example.com",
+ },
+ },
+ Backend: "infra-backend-v1",
+ Namespace: ns,
+ },
+ }
+ for i := range testCases {
+ tc := testCases[i]
+ t.Run(tc.GetTestCaseName(i), func(t *testing.T) {
+ t.Parallel()
+ http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, tc)
+ })
+ }
+ },
+}
diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml
index 5499e8824e..9a9f095401 100644
--- a/test/helm/gateway-crds-helm/all.out.yaml
+++ b/test/helm/gateway-crds-helm/all.out.yaml
@@ -25157,6 +25157,35 @@ spec:
Defaults to true.
type: boolean
type: object
+ endpointHostname:
+ description: |-
+ EndpointHostname configures the hostname value attached to backend endpoints.
+ If unset, no hostname is attached to Kubernetes Service endpoints.
+ properties:
+ hostname:
+ description: |-
+ Hostname is a custom static hostname to attach to backend endpoints.
+ This field is required when type is "Static" and must not be set for other types.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ type:
+ description: Type determines how endpoint hostnames should be
+ populated.
+ enum:
+ - None
+ - KubernetesService
+ - Static
+ type: string
+ required:
+ - type
+ type: object
+ x-kubernetes-validations:
+ - message: hostname must be set when type is Static
+ rule: 'self.type == ''Static'' ? has(self.hostname) : true'
+ - message: hostname must not be set when type is not Static
+ rule: 'self.type != ''Static'' ? !has(self.hostname) : true'
faultInjection:
description: |-
FaultInjection defines the fault injection policy to be applied. This configuration can be used to
diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml
index 194623aac3..2e5e852087 100644
--- a/test/helm/gateway-crds-helm/e2e.out.yaml
+++ b/test/helm/gateway-crds-helm/e2e.out.yaml
@@ -1095,6 +1095,35 @@ spec:
Defaults to true.
type: boolean
type: object
+ endpointHostname:
+ description: |-
+ EndpointHostname configures the hostname value attached to backend endpoints.
+ If unset, no hostname is attached to Kubernetes Service endpoints.
+ properties:
+ hostname:
+ description: |-
+ Hostname is a custom static hostname to attach to backend endpoints.
+ This field is required when type is "Static" and must not be set for other types.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ type:
+ description: Type determines how endpoint hostnames should be
+ populated.
+ enum:
+ - None
+ - KubernetesService
+ - Static
+ type: string
+ required:
+ - type
+ type: object
+ x-kubernetes-validations:
+ - message: hostname must be set when type is Static
+ rule: 'self.type == ''Static'' ? has(self.hostname) : true'
+ - message: hostname must not be set when type is not Static
+ rule: 'self.type != ''Static'' ? !has(self.hostname) : true'
faultInjection:
description: |-
FaultInjection defines the fault injection policy to be applied. This configuration can be used to
diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
index 5e3951fca6..d0baac9875 100644
--- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
+++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml
@@ -1095,6 +1095,35 @@ spec:
Defaults to true.
type: boolean
type: object
+ endpointHostname:
+ description: |-
+ EndpointHostname configures the hostname value attached to backend endpoints.
+ If unset, no hostname is attached to Kubernetes Service endpoints.
+ properties:
+ hostname:
+ description: |-
+ Hostname is a custom static hostname to attach to backend endpoints.
+ This field is required when type is "Static" and must not be set for other types.
+ maxLength: 253
+ minLength: 1
+ pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+ type: string
+ type:
+ description: Type determines how endpoint hostnames should be
+ populated.
+ enum:
+ - None
+ - KubernetesService
+ - Static
+ type: string
+ required:
+ - type
+ type: object
+ x-kubernetes-validations:
+ - message: hostname must be set when type is Static
+ rule: 'self.type == ''Static'' ? has(self.hostname) : true'
+ - message: hostname must not be set when type is not Static
+ rule: 'self.type != ''Static'' ? !has(self.hostname) : true'
faultInjection:
description: |-
FaultInjection defines the fault injection policy to be applied. This configuration can be used to