Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/v1alpha1/backendtrafficpolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt the HTTPRouteFilter or Backend resource solve this use case

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it needs to be explicit, there is no option for it to follow kuberentes service fqdn

}

type BackendTelemetry struct {
Expand Down
34 changes: 34 additions & 0 deletions api/v1alpha1/shared_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]+))`
Expand Down
25 changes: 25 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions internal/cmd/egctl/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
138 changes: 138 additions & 0 deletions internal/gatewayapi/backendtrafficpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
Loading