Skip to content

Commit

Permalink
Merge branch 'main' into jjtimmons/reduce-export-freq
Browse files Browse the repository at this point in the history
  • Loading branch information
Joshua Timmons authored Aug 25, 2023
2 parents 6e88c75 + ecdcde4 commit 4a38e47
Show file tree
Hide file tree
Showing 12 changed files with 1,840 additions and 1,172 deletions.
3 changes: 3 additions & 0 deletions .changelog/18583.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
mesh: **(Enterprise only)** Adds rate limiting config to service-defaults
```
6 changes: 5 additions & 1 deletion agent/configentry/merge_service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"fmt"

"github.com/hashicorp/go-hclog"
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/go-memdb"
"github.com/imdario/mergo"
"github.com/mitchellh/copystructure"

Expand Down Expand Up @@ -141,6 +141,10 @@ func MergeServiceConfig(defaults *structs.ServiceConfigResponse, service *struct
ns.Proxy.EnvoyExtensions = nsExtensions
}

if ratelimit := defaults.RateLimits.ToEnvoyExtension(); ratelimit != nil {
ns.Proxy.EnvoyExtensions = append(ns.Proxy.EnvoyExtensions, *ratelimit)
}

if ns.Proxy.MeshGateway.Mode == structs.MeshGatewayModeDefault {
ns.Proxy.MeshGateway.Mode = defaults.MeshGateway.Mode
}
Expand Down
108 changes: 108 additions & 0 deletions agent/configentry/merge_service_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -972,3 +972,111 @@ func Test_MergeServiceConfig_UpstreamOverrides(t *testing.T) {
})
}
}

// Tests that RateLimit config is a no-op in non-enterprise.
// In practice, the ratelimit config would have been validated
// on write.
func Test_MergeServiceConfig_RateLimit(t *testing.T) {
rl := structs.RateLimits{
InstanceLevel: structs.InstanceLevelRateLimits{
RequestsPerSecond: 1234,
RequestsMaxBurst: 2345,
Routes: []structs.InstanceLevelRouteRateLimits{
{
PathExact: "/admin",
RequestsPerSecond: 3333,
RequestsMaxBurst: 4444,
},
},
},
}
tests := []struct {
name string
defaults *structs.ServiceConfigResponse
service *structs.NodeService
want *structs.NodeService
}{
{
name: "injects ratelimit extension",
defaults: &structs.ServiceConfigResponse{
RateLimits: rl,
},
service: &structs.NodeService{
ID: "foo-proxy",
Service: "foo-proxy",
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "foo",
DestinationServiceID: "foo",
},
},
want: &structs.NodeService{
ID: "foo-proxy",
Service: "foo-proxy",
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "foo",
DestinationServiceID: "foo",
EnvoyExtensions: func() []structs.EnvoyExtension {
if ext := rl.ToEnvoyExtension(); ext != nil {
return []structs.EnvoyExtension{*ext}
}
return nil
}(),
},
},
},
{
name: "injects ratelimit extension at the end",
defaults: &structs.ServiceConfigResponse{
RateLimits: rl,
EnvoyExtensions: []structs.EnvoyExtension{
{
Name: "existing-ext",
Required: true,
Arguments: map[string]interface{}{
"arg1": "val1",
},
},
},
},
service: &structs.NodeService{
ID: "foo-proxy",
Service: "foo-proxy",
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "foo",
DestinationServiceID: "foo",
},
},

want: &structs.NodeService{
ID: "foo-proxy",
Service: "foo-proxy",
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "foo",
DestinationServiceID: "foo",
EnvoyExtensions: func() []structs.EnvoyExtension {
existing := []structs.EnvoyExtension{
{
Name: "existing-ext",
Required: true,
Arguments: map[string]interface{}{
"arg1": "val1",
},
},
}
if ext := rl.ToEnvoyExtension(); ext != nil {
existing = append(existing, *ext)
}
return existing
}(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := MergeServiceConfig(tt.defaults, tt.service)
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
3 changes: 3 additions & 0 deletions agent/configentry/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ func ComputeResolvedServiceConfig(
if serviceConf.Destination != nil {
thisReply.Destination = *serviceConf.Destination
}
if serviceConf.RateLimits != nil {
thisReply.RateLimits = *serviceConf.RateLimits
}

// Populate values for the proxy config map
proxyConf := thisReply.ProxyConfig
Expand Down
52 changes: 47 additions & 5 deletions agent/structs/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ type ServiceConfigEntry struct {
LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"`
LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"`
BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"`
RateLimits *RateLimits `json:",omitempty" alias:"rate_limits"`
EnvoyExtensions EnvoyExtensions `json:",omitempty" alias:"envoy_extensions"`

Meta map[string]string `json:",omitempty"`
Expand Down Expand Up @@ -286,6 +287,10 @@ func (e *ServiceConfigEntry) Validate() error {
}
}

if err := validateRatelimit(e.RateLimits); err != nil {
validationErr = multierror.Append(validationErr, err)
}

if err := envoyextensions.ValidateExtensions(e.EnvoyExtensions.ToAPI()); err != nil {
validationErr = multierror.Append(validationErr, err)
}
Expand Down Expand Up @@ -382,16 +387,52 @@ type DestinationConfig struct {
Port int `json:",omitempty"`
}

func IsHostname(address string) bool {
ip := net.ParseIP(address)
return ip == nil
}

func IsIP(address string) bool {
ip := net.ParseIP(address)
return ip != nil
}

// RateLimits is rate limiting configuration that is applied to
// inbound traffic for a service.
// Rate limiting is a Consul enterprise feature.
type RateLimits struct {
InstanceLevel InstanceLevelRateLimits `alias:"instance_level"`
}

// InstanceLevelRateLimits represents rate limit configuration
// that are applied per service instance.
type InstanceLevelRateLimits struct {
// RequestsPerSecond is the average number of requests per second that can be
// made without being throttled. This field is required if RequestsMaxBurst
// is set. The allowed number of requests may exceed RequestsPerSecond up to
// the value specified in RequestsMaxBurst.
//
// Internally, this is the refill rate of the token bucket used for rate limiting.
RequestsPerSecond int `alias:"requests_per_second"`

// RequestsMaxBurst is the maximum number of requests that can be sent
// in a burst. Should be equal to or greater than RequestsPerSecond.
// If unset, defaults to RequestsPerSecond.
//
// Internally, this is the maximum size of the token bucket used for rate limiting.
RequestsMaxBurst int `alias:"requests_max_burst"`

// Routes is a list of rate limits applied to specific routes.
// Overrides any top-level configuration.
Routes []InstanceLevelRouteRateLimits
}

// InstanceLevelRouteRateLimits represents rate limit configuration
// applied to a route matching one of PathExact/PathPrefix/PathRegex.
type InstanceLevelRouteRateLimits struct {
PathExact string `alias:"path_exact"`
PathPrefix string `alias:"path_prefix"`
PathRegex string `alias:"path_regex"`

RequestsPerSecond int `alias:"requests_per_second"`
RequestsMaxBurst int `alias:"requests_max_burst"`
}

// ProxyConfigEntry is the top-level struct for global proxy configuration defaults.
type ProxyConfigEntry struct {
Kind string
Expand Down Expand Up @@ -1218,6 +1259,7 @@ type ServiceConfigResponse struct {
Mode ProxyMode `json:",omitempty"`
Destination DestinationConfig `json:",omitempty"`
AccessLogs AccessLogsConfig `json:",omitempty"`
RateLimits RateLimits `json:",omitempty"`
Meta map[string]string `json:",omitempty"`
EnvoyExtensions []EnvoyExtension `json:",omitempty"`
QueryMeta
Expand Down
9 changes: 9 additions & 0 deletions agent/structs/config_entry_ce.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ func validateExportedServicesName(name string) error {
func makeEnterpriseConfigEntry(kind, name string) ConfigEntry {
return nil
}

func validateRatelimit(rl *RateLimits) error {
if rl != nil {
return fmt.Errorf("invalid rate_limits config. Rate limiting is a consul enterprise feature")
}
return nil
}

func (rl RateLimits) ToEnvoyExtension() *EnvoyExtension { return nil }
12 changes: 12 additions & 0 deletions agent/structs/structs.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,14 @@ func (o *ServiceConfigEntry) DeepCopy() *ServiceConfigEntry {
copy(cp.Destination.Addresses, o.Destination.Addresses)
}
}
if o.RateLimits != nil {
cp.RateLimits = new(RateLimits)
*cp.RateLimits = *o.RateLimits
if o.RateLimits.InstanceLevel.Routes != nil {
cp.RateLimits.InstanceLevel.Routes = make([]InstanceLevelRouteRateLimits, len(o.RateLimits.InstanceLevel.Routes))
copy(cp.RateLimits.InstanceLevel.Routes, o.RateLimits.InstanceLevel.Routes)
}
}
if o.EnvoyExtensions != nil {
cp.EnvoyExtensions = make([]EnvoyExtension, len(o.EnvoyExtensions))
copy(cp.EnvoyExtensions, o.EnvoyExtensions)
Expand Down Expand Up @@ -947,6 +955,10 @@ func (o *ServiceConfigResponse) DeepCopy() *ServiceConfigResponse {
cp.Destination.Addresses = make([]string, len(o.Destination.Addresses))
copy(cp.Destination.Addresses, o.Destination.Addresses)
}
if o.RateLimits.InstanceLevel.Routes != nil {
cp.RateLimits.InstanceLevel.Routes = make([]InstanceLevelRouteRateLimits, len(o.RateLimits.InstanceLevel.Routes))
copy(cp.RateLimits.InstanceLevel.Routes, o.RateLimits.InstanceLevel.Routes)
}
if o.Meta != nil {
cp.Meta = make(map[string]string, len(o.Meta))
for k2, v2 := range o.Meta {
Expand Down
42 changes: 42 additions & 0 deletions api/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,47 @@ type UpstreamLimits struct {
MaxConcurrentRequests *int `alias:"max_concurrent_requests"`
}

// RateLimits is rate limiting configuration that is applied to
// inbound traffic for a service.
// Rate limiting is a Consul enterprise feature.
type RateLimits struct {
InstanceLevel InstanceLevelRateLimits `alias:"instance_level"`
}

// InstanceLevelRateLimits represents rate limit configuration
// that are applied per service instance.
type InstanceLevelRateLimits struct {
// RequestsPerSecond is the average number of requests per second that can be
// made without being throttled. This field is required if RequestsMaxBurst
// is set. The allowed number of requests may exceed RequestsPerSecond up to
// the value specified in RequestsMaxBurst.
//
// Internally, this is the refill rate of the token bucket used for rate limiting.
RequestsPerSecond int `alias:"requests_per_second"`

// RequestsMaxBurst is the maximum number of requests that can be sent
// in a burst. Should be equal to or greater than RequestsPerSecond.
// If unset, defaults to RequestsPerSecond.
//
// Internally, this is the maximum size of the token bucket used for rate limiting.
RequestsMaxBurst int `alias:"requests_max_burst"`

// Routes is a list of rate limits applied to specific routes.
// Overrides any top-level configuration.
Routes []InstanceLevelRouteRateLimits
}

// InstanceLevelRouteRateLimits represents rate limit configuration
// applied to a route matching one of PathExact/PathPrefix/PathRegex.
type InstanceLevelRouteRateLimits struct {
PathExact string `alias:"path_exact"`
PathPrefix string `alias:"path_prefix"`
PathRegex string `alias:"path_regex"`

RequestsPerSecond int `alias:"requests_per_second"`
RequestsMaxBurst int `alias:"requests_max_burst"`
}

type ServiceConfigEntry struct {
Kind string
Name string
Expand All @@ -332,6 +373,7 @@ type ServiceConfigEntry struct {
LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"`
LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"`
BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"`
RateLimits *RateLimits `json:",omitempty" alias:"rate_limits"`
EnvoyExtensions []EnvoyExtension `json:",omitempty" alias:"envoy_extensions"`
Meta map[string]string `json:",omitempty"`
CreateIndex uint64
Expand Down
Loading

0 comments on commit 4a38e47

Please sign in to comment.