Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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/envoygateway_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ func (e *EnvoyGateway) TopologyInjectorDisabled() bool {
return false
}

// GetEnvoyProxyDefaultSpec returns the default EnvoyProxySpec if specified,
// otherwise returns nil.
func (e *EnvoyGateway) GetEnvoyProxyDefaultSpec() *EnvoyProxySpec {
return e.EnvoyProxy
}

// defaultRuntimeFlags are the default runtime flags for Envoy Gateway.
var defaultRuntimeFlags = map[RuntimeFlag]bool{
XDSNameSchemeV2: false,
Expand Down
17 changes: 17 additions & 0 deletions api/v1alpha1/envoygateway_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ type EnvoyGatewaySpec struct {
// RuntimeFlags defines the runtime flags for Envoy Gateway.
// Unlike ExtensionAPIs, these flags are temporary and will be removed in future releases once the related features are stable.
RuntimeFlags *RuntimeFlags `json:"runtimeFlags,omitempty"`

// EnvoyProxy defines the default EnvoyProxy configuration that applies
// to all managed Envoy Proxy fleet. This is an optional field and when
// provided, the settings from this EnvoyProxySpec serve as the base
// defaults for all Envoy Proxy instances.
//
// The hierarchy for EnvoyProxy configuration is (highest to lowest priority):
// 1. Gateway-level EnvoyProxy (referenced via Gateway.spec.infrastructure.parametersRef)
// 2. GatewayClass-level EnvoyProxy (referenced via GatewayClass.spec.parametersRef)
// 3. This EnvoyProxy default spec
//
// Currently, the most specific EnvoyProxy configuration wins completely (replace semantics).
// A future release will introduce merge semantics to allow combining configurations
// across multiple levels.
//
// +optional
EnvoyProxy *EnvoyProxySpec `json:"envoyProxy,omitempty"`
}

// GatewayAPI defines an experimental Gateway API resource that can be enabled.
Expand Down
5 changes: 5 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.

19 changes: 18 additions & 1 deletion internal/gatewayapi/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func (g *GatewayContext) ResetListeners() {
}

func (g *GatewayContext) attachEnvoyProxy(resources *resource.Resources, epMap map[types.NamespacedName]*egv1a1.EnvoyProxy) {
// Priority order (highest to lowest):
// 1. Gateway-level EnvoyProxy (via parametersRef)
// 2. GatewayClass-level EnvoyProxy
// 3. Default EnvoyProxySpec from EnvoyGateway configuration

if g.Spec.Infrastructure != nil && g.Spec.Infrastructure.ParametersRef != nil && !IsMergeGatewaysEnabled(resources) {
ref := g.Spec.Infrastructure.ParametersRef
if string(ref.Group) == egv1a1.GroupVersion.Group && ref.Kind == egv1a1.KindEnvoyProxy {
Expand All @@ -63,7 +68,19 @@ func (g *GatewayContext) attachEnvoyProxy(resources *resource.Resources, epMap m
// not found, fallthrough to use envoyProxy attached to gatewayclass
}

g.envoyProxy = resources.EnvoyProxyForGatewayClass
// Use GatewayClass-level EnvoyProxy if available
if resources.EnvoyProxyForGatewayClass != nil {
g.envoyProxy = resources.EnvoyProxyForGatewayClass
return
}

// Fall back to default EnvoyProxySpec from EnvoyGateway configuration
if resources.EnvoyProxyDefaultSpec != nil {
// Create a synthetic EnvoyProxy object from the default spec
g.envoyProxy = &egv1a1.EnvoyProxy{
Spec: *resources.EnvoyProxyDefaultSpec,
}
}
}

// ListenerContext wraps a Listener and provides helper methods for
Expand Down
244 changes: 244 additions & 0 deletions internal/gatewayapi/contexts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import (

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
"github.com/envoyproxy/gateway/internal/gatewayapi/status"
)

Expand Down Expand Up @@ -150,3 +154,243 @@ func TestContextsStaleListener(t *testing.T) {
expectedGCtxListeners := []*ListenerContext{httpsListenerCtx}
require.Equal(t, expectedGCtxListeners, gCtx.listeners)
}

func TestAttachEnvoyProxy(t *testing.T) {
testCases := []struct {
name string
gatewayParametersRef *gwapiv1.LocalParametersReference
envoyProxyForGateway *egv1a1.EnvoyProxy
envoyProxyForGWClass *egv1a1.EnvoyProxy
envoyProxyDefaultSpec *egv1a1.EnvoyProxySpec
expectedMergeGateways *bool
expectedConcurrency *int32
expectEnvoyProxyNil bool
}{
{
name: "no envoy proxy at any level",
expectEnvoyProxyNil: true,
},
{
name: "only default spec - should use default",
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](4),
},
expectedConcurrency: ptr.To[int32](4),
},
{
name: "gatewayclass envoy proxy overrides default spec",
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "envoy-gateway-system",
Name: "gc-proxy",
},
Spec: egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](8),
},
},
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](4),
},
expectedConcurrency: ptr.To[int32](8),
},
{
name: "gateway envoy proxy overrides gatewayclass",
gatewayParametersRef: &gwapiv1.LocalParametersReference{
Group: gwapiv1.Group(egv1a1.GroupVersion.Group),
Kind: gwapiv1.Kind(egv1a1.KindEnvoyProxy),
Name: "gw-proxy",
},
envoyProxyForGateway: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "gw-proxy",
},
Spec: egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](16),
},
},
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "envoy-gateway-system",
Name: "gc-proxy",
},
Spec: egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](8),
},
},
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](4),
},
expectedConcurrency: ptr.To[int32](16),
},
{
name: "default spec with merge gateways enabled",
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
Concurrency: ptr.To[int32](4),
},
expectedMergeGateways: ptr.To(true),
expectedConcurrency: ptr.To[int32](4),
},
{
name: "gatewayclass overrides default merge gateways setting",
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
ObjectMeta: metav1.ObjectMeta{
Namespace: "envoy-gateway-system",
Name: "gc-proxy",
},
Spec: egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(false),
},
},
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
},
expectedMergeGateways: ptr.To(false),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create gateway
gateway := &gwapiv1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "test-gateway",
},
Spec: gwapiv1.GatewaySpec{
GatewayClassName: "test-gc",
},
}
if tc.gatewayParametersRef != nil {
gateway.Spec.Infrastructure = &gwapiv1.GatewayInfrastructure{
ParametersRef: tc.gatewayParametersRef,
}
}

gCtx := &GatewayContext{Gateway: gateway}

// Build resources
resources := &resource.Resources{
EnvoyProxyForGatewayClass: tc.envoyProxyForGWClass,
EnvoyProxyDefaultSpec: tc.envoyProxyDefaultSpec,
}

// Build envoy proxy map for gateway-level proxies
epMap := make(map[types.NamespacedName]*egv1a1.EnvoyProxy)
if tc.envoyProxyForGateway != nil {
key := types.NamespacedName{
Namespace: tc.envoyProxyForGateway.Namespace,
Name: tc.envoyProxyForGateway.Name,
}
epMap[key] = tc.envoyProxyForGateway
}

// Call attachEnvoyProxy
gCtx.attachEnvoyProxy(resources, epMap)

// Verify results
if tc.expectEnvoyProxyNil {
require.Nil(t, gCtx.envoyProxy)
return
}

require.NotNil(t, gCtx.envoyProxy)

if tc.expectedConcurrency != nil {
require.NotNil(t, gCtx.envoyProxy.Spec.Concurrency)
require.Equal(t, *tc.expectedConcurrency, *gCtx.envoyProxy.Spec.Concurrency)
}

if tc.expectedMergeGateways != nil {
require.NotNil(t, gCtx.envoyProxy.Spec.MergeGateways)
require.Equal(t, *tc.expectedMergeGateways, *gCtx.envoyProxy.Spec.MergeGateways)
}
})
}
}

func TestIsMergeGatewaysEnabled(t *testing.T) {
testCases := []struct {
name string
envoyProxyForGWClass *egv1a1.EnvoyProxy
envoyProxyDefaultSpec *egv1a1.EnvoyProxySpec
expected bool
}{
{
name: "no envoy proxy configured",
expected: false,
},
{
name: "default spec with merge gateways true",
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
},
expected: true,
},
{
name: "default spec with merge gateways false",
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(false),
},
expected: false,
},
{
name: "gatewayclass proxy with merge gateways true",
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
Spec: egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
},
},
expected: true,
},
{
name: "gatewayclass proxy overrides default - gc true, default false",
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
Spec: egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
},
},
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(false),
},
expected: true,
},
{
name: "gatewayclass proxy overrides default - gc false, default true",
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
Spec: egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(false),
},
},
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
},
expected: false,
},
{
name: "gatewayclass proxy nil merge gateways falls back to default",
envoyProxyForGWClass: &egv1a1.EnvoyProxy{
Spec: egv1a1.EnvoyProxySpec{
Concurrency: ptr.To[int32](4), // some other setting
},
},
envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
MergeGateways: ptr.To(true),
},
expected: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resources := &resource.Resources{
EnvoyProxyForGatewayClass: tc.envoyProxyForGWClass,
EnvoyProxyDefaultSpec: tc.envoyProxyDefaultSpec,
}

result := IsMergeGatewaysEnabled(resources)
require.Equal(t, tc.expected, result)
})
}
}
14 changes: 13 additions & 1 deletion internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,19 @@ func irTLSCrlName(namespace, name string) string {
}

func IsMergeGatewaysEnabled(resources *resource.Resources) bool {
return resources.EnvoyProxyForGatewayClass != nil && resources.EnvoyProxyForGatewayClass.Spec.MergeGateways != nil && *resources.EnvoyProxyForGatewayClass.Spec.MergeGateways
// Check GatewayClass-level EnvoyProxy first (higher priority)
if resources.EnvoyProxyForGatewayClass != nil &&
resources.EnvoyProxyForGatewayClass.Spec.MergeGateways != nil {
return *resources.EnvoyProxyForGatewayClass.Spec.MergeGateways
}

// Fall back to default EnvoyProxySpec from EnvoyGateway configuration
if resources.EnvoyProxyDefaultSpec != nil &&
resources.EnvoyProxyDefaultSpec.MergeGateways != nil {
return *resources.EnvoyProxyDefaultSpec.MergeGateways
}

return false
}

func protocolSliceToStringSlice(protocols []gwapiv1.ProtocolType) []string {
Expand Down
3 changes: 3 additions & 0 deletions internal/gatewayapi/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type Resources struct {
EnvoyProxyForGatewayClass *egv1a1.EnvoyProxy `json:"envoyProxyForGatewayClass,omitempty" yaml:"envoyProxyForGatewayClass,omitempty"`
// EnvoyProxiesForGateways holds EnvoyProxiesForGateways attached to Gateways
EnvoyProxiesForGateways []*egv1a1.EnvoyProxy `json:"envoyProxiesForGateways,omitempty" yaml:"envoyProxiesForGateways,omitempty"`
// EnvoyProxyDefaultSpec holds the default EnvoyProxySpec from EnvoyGateway configuration.
// This serves as the lowest priority fallback when no GatewayClass or Gateway level EnvoyProxy is specified.
EnvoyProxyDefaultSpec *egv1a1.EnvoyProxySpec `json:"envoyProxyDefaultSpec,omitempty" yaml:"envoyProxyDefaultSpec,omitempty"`

GatewayClass *gwapiv1.GatewayClass `json:"gatewayClass,omitempty" yaml:"gatewayClass,omitempty"`
Gateways []*gwapiv1.Gateway `json:"gateways,omitempty" yaml:"gateways,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions internal/gatewayapi/resource/zz_generated.deepcopy.go

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

Loading
Loading