Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

Add max connections configuration to GatewayClassConfig CRD #405

Merged
merged 5 commits into from
Oct 27, 2022
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
3 changes: 3 additions & 0 deletions .changelog/405.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
Add optional configuration for maximum upstream connections to GatewayClassConfig CRD. If unset, behavior is unchanged and Envoy's default will be used.
```
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ spec:
spec:
description: Spec defines the desired state of GatewayClassConfig.
properties:
connectionManagement:
description: Configuration information for managing connections in
Envoy
properties:
maxConnections:
Copy link
Member Author

Choose a reason for hiding this comment

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

connectionManagement.maxConnections is somewhat long; however, I opted not to abbreviate to something like connMgmt.maxConns since we haven't abbreviated any other parts of the spec.

description: The maximum number of connections allowed for the
Gateway proxy. If not set, the default for the proxy implementation
will be used.
format: int32
type: integer
type: object
consul:
description: Configuration information about connecting to Consul.
properties:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/go-logr/logr v1.2.3
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/hashicorp/consul/api v1.15.2
github.com/hashicorp/consul/api v1.15.3
github.com/hashicorp/consul/sdk v0.11.0
github.com/hashicorp/go-hclog v1.3.1
github.com/hashicorp/go-multierror v1.1.1
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,14 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.10.1-0.20221004184615-1b565444beb0 h1:UpfUuCYnNyH9vmraU5pU9W3+c7pM7J1kFch349xUBKs=
github.com/hashicorp/consul/api v1.10.1-0.20221004184615-1b565444beb0/go.mod h1:6ME4zGymdgiRmG9otAmUJaFvduqyB+xxix/tpxBjZhg=
github.com/hashicorp/consul/api v1.10.1-0.20221004201501-a3be5a5a825c h1:JQzj2qHlP+M3GuUJ66iKt+0JdJkr7R1rI1BNNYdFQK4=
github.com/hashicorp/consul/api v1.10.1-0.20221004201501-a3be5a5a825c/go.mod h1:6ME4zGymdgiRmG9otAmUJaFvduqyB+xxix/tpxBjZhg=
github.com/hashicorp/consul/api v1.15.2 h1:3Q/pDqvJ7udgt/60QOOW/p/PeKioQN+ncYzzCdN2av0=
github.com/hashicorp/consul/api v1.15.2/go.mod h1:v6nvB10borjOuIwNRZYPZiHKrTM/AyrGtd0WVVodKM8=
github.com/hashicorp/consul/api v1.15.3 h1:WYONYL2rxTXtlekAqblR2SCdJsizMDIj/uXb5wNy9zU=
github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY=
github.com/hashicorp/consul/sdk v0.11.0 h1:HRzj8YSCln2yGgCumN5CL8lYlD3gBurnervJRJAZyC4=
github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand Down
6 changes: 6 additions & 0 deletions internal/adapters/consul/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ func discoveryChain(gateway core.ResolvedGateway) (*api.IngressGatewayConfigEntr
Namespace: gateway.ID.ConsulNamespace,
Meta: gateway.Meta,
}
if gateway.MaxConnections != nil {
ingress.Defaults = &api.IngressServiceConfig{
MaxConnections: gateway.MaxConnections,
}
}

routers := consul.NewConfigEntryIndex(api.ServiceRouter)
splitters := consul.NewConfigEntryIndex(api.ServiceSplitter)
defaults := consul.NewConfigEntryIndex(api.ServiceDefaults)
Expand Down
7 changes: 4 additions & 3 deletions internal/core/resolved.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ type GatewayID struct {
}

type ResolvedGateway struct {
ID GatewayID
Meta map[string]string
Listeners []ResolvedListener
ID GatewayID
Meta map[string]string
Listeners []ResolvedListener
MaxConnections *uint32
Copy link
Member Author

Choose a reason for hiding this comment

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

The ResolvedGateway is what we have access to when deriving the ingress-gateway config entry that we're sending to Consul, so it needs to be aware of any config that we depend on there. This is internal, so it can evolve in the future if we wind up having other connection management config values to pipe through.

}
8 changes: 4 additions & 4 deletions internal/k8s/reconciler/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@ func NewDeployer(config DeployerConfig) *GatewayDeployer {
}

func (d *GatewayDeployer) Deploy(ctx context.Context, gateway *K8sGateway) error {
if err := d.ensureServiceAccount(ctx, gateway.config, gateway.Gateway); err != nil {
if err := d.ensureServiceAccount(ctx, gateway.Config, gateway.Gateway); err != nil {
return err
}

if err := d.ensureSecret(ctx, gateway.config, gateway.Gateway); err != nil {
if err := d.ensureSecret(ctx, gateway.Config, gateway.Gateway); err != nil {
return err
}

if err := d.ensureDeployment(ctx, gateway.GatewayState.ConsulNamespace, gateway.config, gateway.Gateway); err != nil {
if err := d.ensureDeployment(ctx, gateway.GatewayState.ConsulNamespace, gateway.Config, gateway.Gateway); err != nil {
return err
}

return d.ensureService(ctx, gateway.config, gateway.Gateway)
return d.ensureService(ctx, gateway.Config, gateway.Gateway)
}

func (d *GatewayDeployer) ensureServiceAccount(ctx context.Context, config apigwv1alpha1.GatewayClassConfig, gateway *gwv1beta1.Gateway) error {
Expand Down
30 changes: 22 additions & 8 deletions internal/k8s/reconciler/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import (
apigwv1alpha1 "github.com/hashicorp/consul-api-gateway/pkg/apis/v1alpha1"
)

const defaultListenerName = "default"
const (
defaultListenerName = "default"

gatewayMetaExternalSource = "external-source"
gatewayMetaName = "consul-api-gateway/k8s/Gateway.Name"
gatewayMetaNamespace = "consul-api-gateway/k8s/Gateway.Namespace"
)

var (
_ store.Gateway = (*K8sGateway)(nil)
Expand All @@ -20,15 +26,15 @@ type K8sGateway struct {
*gwv1beta1.Gateway
GatewayState *state.GatewayState

config apigwv1alpha1.GatewayClassConfig
Config apigwv1alpha1.GatewayClassConfig
Copy link
Member Author

Choose a reason for hiding this comment

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

Exported this so that the JSON marshaler includes it when we serialize for writing to the store backend.

If not exported, we don't have any idea what the max connections value is when rehydrating from the store and syncing into Consul, which we do at an interval in the background for resiliency.

}

// newK8sGateway
func newK8sGateway(config apigwv1alpha1.GatewayClassConfig, gateway *gwv1beta1.Gateway, gatewayState *state.GatewayState) *K8sGateway {
return &K8sGateway{
Gateway: gateway,
GatewayState: gatewayState,
config: config,
Config: config,
}
}

Expand All @@ -40,22 +46,30 @@ func (g *K8sGateway) ID() core.GatewayID {
}

func (g *K8sGateway) Resolve() core.ResolvedGateway {
listeners := []core.ResolvedListener{}
var listeners []core.ResolvedListener
for i, listener := range g.Gateway.Spec.Listeners {
state := g.GatewayState.Listeners[i]
if state.Valid() {
listeners = append(listeners, g.resolveListener(state, listener))
}
}
return core.ResolvedGateway{

rgw := core.ResolvedGateway{
ID: g.ID(),
Meta: map[string]string{
"external-source": "consul-api-gateway",
"consul-api-gateway/k8s/Gateway.Name": g.Gateway.Name,
"consul-api-gateway/k8s/Gateway.Namespace": g.Gateway.Namespace,
gatewayMetaExternalSource: "consul-api-gateway",
gatewayMetaName: g.Gateway.Name,
gatewayMetaNamespace: g.Gateway.Namespace,
},
Listeners: listeners,
}

if g.Config.Spec.ConnectionManagement.MaxConnections != nil {
maxConns := uint32(*g.Config.Spec.ConnectionManagement.MaxConnections)
rgw.MaxConnections = &maxConns
}

return rgw
}

func (g *K8sGateway) resolveListener(state *state.ListenerState, listener gwv1beta1.Listener) core.ResolvedListener {
Expand Down
28 changes: 28 additions & 0 deletions internal/k8s/reconciler/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package reconciler
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

internalCore "github.com/hashicorp/consul-api-gateway/internal/core"
Expand All @@ -28,3 +30,29 @@ func TestGatewayID(t *testing.T) {
gateway := newK8sGateway(apigwv1alpha1.GatewayClassConfig{}, gw, gwState)
require.Equal(t, internalCore.GatewayID{Service: "name", ConsulNamespace: "consul"}, gateway.ID())
}

func TestK8sGateway_Resolve(t *testing.T) {
t.Parallel()

gcc := apigwv1alpha1.GatewayClassConfig{}

gw := &gwv1beta1.Gateway{
ObjectMeta: meta.ObjectMeta{Name: "name", Namespace: "namespace"},
}

gwState := state.InitialGatewayState(gw)
gwState.ConsulNamespace = "consul"

// Verify max connections not set if unset on GatewayClassConfig
resolvedGateway := newK8sGateway(gcc, gw, gwState).Resolve()
assert.Nil(t, resolvedGateway.MaxConnections)
assert.Equal(t, "consul-api-gateway", resolvedGateway.Meta[gatewayMetaExternalSource])
assert.Equal(t, "name", resolvedGateway.Meta[gatewayMetaName])
assert.Equal(t, "namespace", resolvedGateway.Meta[gatewayMetaNamespace])

// Verify max connections set if set on GatewayClassConfig
gcc.Spec.ConnectionManagement.MaxConnections = pointer.Int32(100)
resolvedGateway = newK8sGateway(gcc, gw, gwState).Resolve()
require.NotNil(t, resolvedGateway.MaxConnections)
assert.EqualValues(t, 100, *resolvedGateway.MaxConnections)
}
60 changes: 53 additions & 7 deletions internal/k8s/reconciler/marshaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,72 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
"k8s.io/utils/pointer"
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/hashicorp/consul-api-gateway/internal/k8s/reconciler/state"
"github.com/hashicorp/consul-api-gateway/pkg/apis/v1alpha1"
)

func TestMarshalRoute(t *testing.T) {
r := &gwv1alpha2.HTTPRoute{}
r := &gwv1beta1.HTTPRoute{
Spec: gwv1beta1.HTTPRouteSpec{
Rules: []gwv1beta1.HTTPRouteRule{{
Filters: []gwv1beta1.HTTPRouteFilter{
{
Type: gwv1beta1.HTTPRouteFilterURLRewrite,
URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{
Path: &gwv1beta1.HTTPPathModifier{
Type: gwv1beta1.PrefixMatchHTTPPathModifier,
ReplacePrefixMatch: pointer.String("/api/v1"),
},
},
},
}},
},
},
}
r.SetGroupVersionKind(schema.GroupVersionKind{
Kind: "HTTPRoute",
})

route := newK8sRoute(r, state.NewRouteState())

data, err := route.MarshalJSON()
data, err := NewMarshaler().MarshalRoute(route)
require.NoError(t, err)
require.NotEmpty(t, data)

unmarshaled, err := NewMarshaler().UnmarshalRoute(data)
require.NoError(t, err)
require.NotNil(t, unmarshaled)

route, ok := unmarshaled.(*K8sRoute)
require.True(t, ok)
assert.NotNil(t, route)
}

func TestMarshalGateway(t *testing.T) {
g := &gwv1beta1.Gateway{}

gcc := v1alpha1.GatewayClassConfig{
Spec: v1alpha1.GatewayClassConfigSpec{
ConnectionManagement: v1alpha1.ConnectionManagementSpec{
MaxConnections: pointer.Int32(4096)}},
}

gateway := newK8sGateway(gcc, g, state.InitialGatewayState(g))

data, err := NewMarshaler().MarshalGateway(gateway)
require.NoError(t, err)
assert.NotEmpty(t, data)

unmarshaled := &K8sRoute{}
require.NoError(t, unmarshaled.UnmarshalJSON(data))
unmarshaled, err := NewMarshaler().UnmarshalGateway(data)
require.NoError(t, err)
require.NotNil(t, unmarshaled)

_, ok := unmarshaled.Route.(*gwv1alpha2.HTTPRoute)
assert.True(t, ok)
gateway, ok := unmarshaled.(*K8sGateway)
require.True(t, ok)
require.NotNil(t, gateway)
require.NotNil(t, gateway.Config.Spec.ConnectionManagement.MaxConnections)
assert.EqualValues(t, 4096, *gateway.Config.Spec.ConnectionManagement.MaxConnections)
}
10 changes: 10 additions & 0 deletions pkg/apis/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ type GatewayClassConfigSpec struct {
LogLevel string `json:"logLevel,omitempty"`
// Configuration information about how many instances to deploy
DeploymentSpec DeploymentSpec `json:"deployment,omitempty"`
// Configuration information for managing connections in Envoy
ConnectionManagement ConnectionManagementSpec `json:"connectionManagement,omitempty"`
}

// +k8s:deepcopy-gen=true

type ConnectionManagementSpec struct {
// The maximum number of connections allowed for the Gateway proxy.
// If not set, the default for the proxy implementation will be used.
MaxConnections *int32 `json:"maxConnections,omitempty"`
}

// +k8s:deepcopy-gen=true
Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/v1alpha1/zz_generated.deepcopy.go

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