Skip to content
Merged
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/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