Add support for configuring Envoys route idle_timeout (#14340)

* Add idleTimeout

Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com>
Co-authored-by: Dhia Ayachi <dhia@hashicorp.com>
This commit is contained in:
James Oulman 2022-11-29 17:43:15 -05:00 committed by GitHub
parent ecd4307b7c
commit 71f7f2e3dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 144 additions and 1 deletions

4
.changelog/14340.txt Normal file
View File

@ -0,0 +1,4 @@
```release-note:feature
connect: Add local_idle_timeout_ms to allow configuring the Envoy route idle timeout on local_app
connect: Add IdleTimeout to service-router to allow configuring the Envoy route idle timeout
```

View File

@ -4107,6 +4107,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
"namespace" : "leek",
"prefix_rewrite" : "/alternate",
"request_timeout" : "99s",
"idle_timeout" : "99s",
"num_retries" : 12345,
"retry_on_connect_failure": true,
"retry_on_status_codes" : [401, 209]
@ -4195,6 +4196,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
namespace = "leek"
prefix_rewrite = "/alternate"
request_timeout = "99s"
idle_timeout = "99s"
num_retries = 12345
retry_on_connect_failure = true
retry_on_status_codes = [401, 209]
@ -4284,6 +4286,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
Partition: acl.DefaultPartitionName,
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
IdleTimeout: 99 * time.Second,
NumRetries: 12345,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{401, 209},

View File

@ -682,6 +682,15 @@ func setupTestVariationDiscoveryChain(
RequestTimeout: 33 * time.Second,
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/idle-timeout",
}),
Destination: &structs.ServiceRouteDestination{
Service: "idle-timeout",
IdleTimeout: 33 * time.Second,
},
},
{
Match: httpMatch(&structs.ServiceRouteHTTPMatch{
PathPrefix: "/retry-connect",

View File

@ -426,6 +426,10 @@ type ServiceRouteDestination struct {
// downstream request (and retries) to be processed.
RequestTimeout time.Duration `json:",omitempty" alias:"request_timeout"`
// IdleTimeout is The total amount of time permitted for the request stream
// to be idle
IdleTimeout time.Duration `json:",omitempty" alias:"idle_timeout"`
// NumRetries is the number of times to retry the request when a retryable
// result occurs. This seems fairly proxy agnostic.
NumRetries uint32 `json:",omitempty" alias:"num_retries"`
@ -452,15 +456,21 @@ func (e *ServiceRouteDestination) MarshalJSON() ([]byte, error) {
type Alias ServiceRouteDestination
exported := &struct {
RequestTimeout string `json:",omitempty"`
IdleTimeout string `json:",omitempty"`
*Alias
}{
RequestTimeout: e.RequestTimeout.String(),
IdleTimeout: e.IdleTimeout.String(),
Alias: (*Alias)(e),
}
if e.RequestTimeout == 0 {
exported.RequestTimeout = ""
}
if e.IdleTimeout == 0 {
exported.IdleTimeout = ""
}
return json.Marshal(exported)
}
@ -468,6 +478,7 @@ func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
type Alias ServiceRouteDestination
aux := &struct {
RequestTimeout string
IdleTimeout string
*Alias
}{
Alias: (*Alias)(e),
@ -481,6 +492,11 @@ func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
return err
}
}
if aux.IdleTimeout != "" {
if e.IdleTimeout, err = time.ParseDuration(aux.IdleTimeout); err != nil {
return err
}
}
return nil
}

View File

@ -603,6 +603,7 @@ func TestDecodeConfigEntry(t *testing.T) {
namespace = "leek"
prefix_rewrite = "/alternate"
request_timeout = "99s"
idle_timeout = "99s"
num_retries = 12345
retry_on_connect_failure = true
retry_on_status_codes = [401, 209]
@ -704,6 +705,7 @@ func TestDecodeConfigEntry(t *testing.T) {
Namespace = "leek"
PrefixRewrite = "/alternate"
RequestTimeout = "99s"
IdleTimeout = "99s"
NumRetries = 12345
RetryOnConnectFailure = true
RetryOnStatusCodes = [401, 209]
@ -805,6 +807,7 @@ func TestDecodeConfigEntry(t *testing.T) {
Namespace: "leek",
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
IdleTimeout: 99 * time.Second,
NumRetries: 12345,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{401, 209},

View File

@ -49,6 +49,11 @@ type ProxyConfig struct {
// respected (15s)
LocalRequestTimeoutMs *int `mapstructure:"local_request_timeout_ms"`
// LocalIdleTimeoutMs is the number of milliseconds to timeout HTTP streams
// to the local app instance. If not set, no value is set, Envoy defaults are
// respected (300s)
LocalIdleTimeoutMs *int `mapstructure:"local_idle_timeout_ms"`
// Protocol describes the service's protocol. Valid values are "tcp",
// "http" and "grpc". Anything else is treated as tcp. This enables
// protocol aware features like per-request metrics and connection

View File

@ -157,6 +157,39 @@ func TestParseProxyConfig(t *testing.T) {
Protocol: "tcp",
},
},
{
name: "local idle timeout override, float ",
input: map[string]interface{}{
"local_idle_timeout_ms": float64(1000.0),
},
want: ProxyConfig{
LocalConnectTimeoutMs: 5000,
LocalIdleTimeoutMs: intPointer(1000),
Protocol: "tcp",
},
},
{
name: "local idle timeout override, int ",
input: map[string]interface{}{
"local_idle_timeout_ms": 1000,
},
want: ProxyConfig{
LocalConnectTimeoutMs: 5000,
LocalIdleTimeoutMs: intPointer(1000),
Protocol: "tcp",
},
},
{
name: "local idle timeout override, string",
input: map[string]interface{}{
"local_idle_timeout_ms": "1000",
},
want: ProxyConfig{
LocalConnectTimeoutMs: 5000,
LocalIdleTimeoutMs: intPointer(1000),
Protocol: "tcp",
},
},
{
name: "balance inbound connections override, string",
input: map[string]interface{}{

View File

@ -1275,6 +1275,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
routeName: name,
cluster: LocalAppClusterName,
requestTimeoutMs: cfg.LocalRequestTimeoutMs,
idleTimeoutMs: cfg.LocalIdleTimeoutMs,
tracing: tracing,
}
if useHTTPFilter {
@ -2114,6 +2115,7 @@ type listenerFilterOpts struct {
statPrefix string
routePath string
requestTimeoutMs *int
idleTimeoutMs *int
ingressGateway bool
httpAuthzFilter *envoy_http_v3.HttpFilter
forwardClientDetails bool
@ -2260,6 +2262,11 @@ func makeHTTPFilter(opts listenerFilterOpts) (*envoy_listener_v3.Filter, error)
r.Timeout = durationpb.New(time.Duration(*opts.requestTimeoutMs) * time.Millisecond)
}
if opts.idleTimeoutMs != nil {
r := route.GetRoute()
r.IdleTimeout = durationpb.New(time.Duration(*opts.idleTimeoutMs) * time.Millisecond)
}
// If a path is provided, do not match on a catch-all prefix
if opts.routePath != "" {
route.Match.PathSpecifier = &envoy_route_v3.RouteMatch_Path{Path: opts.routePath}

View File

@ -230,6 +230,7 @@ func TestListenersFromSnapshot(t *testing.T) {
ns.Proxy.Config["protocol"] = "http"
ns.Proxy.Config["local_connect_timeout_ms"] = 1234
ns.Proxy.Config["local_request_timeout_ms"] = 2345
ns.Proxy.Config["local_idle_timeout_ms"] = 3456
}, nil)
},
},

View File

@ -577,6 +577,10 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain(
routeAction.Route.Timeout = durationpb.New(destination.RequestTimeout)
}
if destination.IdleTimeout > 0 {
routeAction.Route.IdleTimeout = durationpb.New(destination.IdleTimeout)
}
if destination.HasRetryFeatures() {
routeAction.Route.RetryPolicy = getRetryPolicyForDestination(destination)
}

View File

@ -83,7 +83,8 @@
},
"route": {
"cluster": "local_app",
"timeout": "2.345s"
"timeout": "2.345s",
"idleTimeout": "3.456s"
}
}
]

View File

@ -274,6 +274,15 @@
"timeout": "33s"
}
},
{
"match": {
"prefix": "/idle-timeout"
},
"route": {
"cluster": "idle-timeout.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"idleTimeout": "33s"
}
},
{
"match": {
"prefix": "/retry-connect"

View File

@ -275,6 +275,15 @@
"timeout": "33s"
}
},
{
"match": {
"prefix": "/idle-timeout"
},
"route": {
"cluster": "idle-timeout.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"idleTimeout": "33s"
}
},
{
"match": {
"prefix": "/retry-connect"

View File

@ -275,6 +275,15 @@
"timeout": "33s"
}
},
{
"match": {
"prefix": "/idle-timeout"
},
"route": {
"cluster": "idle-timeout.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul",
"idleTimeout": "33s"
}
},
{
"match": {
"prefix": "/retry-connect"

View File

@ -69,6 +69,7 @@ type ServiceRouteDestination struct {
Partition string `json:",omitempty"`
PrefixRewrite string `json:",omitempty" alias:"prefix_rewrite"`
RequestTimeout time.Duration `json:",omitempty" alias:"request_timeout"`
IdleTimeout time.Duration `json:",omitempty" alias:"idle_timeout"`
NumRetries uint32 `json:",omitempty" alias:"num_retries"`
RetryOnConnectFailure bool `json:",omitempty" alias:"retry_on_connect_failure"`
RetryOnStatusCodes []uint32 `json:",omitempty" alias:"retry_on_status_codes"`
@ -81,14 +82,19 @@ func (e *ServiceRouteDestination) MarshalJSON() ([]byte, error) {
type Alias ServiceRouteDestination
exported := &struct {
RequestTimeout string `json:",omitempty"`
IdleTimeout string `json:",omitempty"`
*Alias
}{
RequestTimeout: e.RequestTimeout.String(),
IdleTimeout: e.IdleTimeout.String(),
Alias: (*Alias)(e),
}
if e.RequestTimeout == 0 {
exported.RequestTimeout = ""
}
if e.IdleTimeout == 0 {
exported.IdleTimeout = ""
}
return json.Marshal(exported)
}
@ -97,6 +103,7 @@ func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
type Alias ServiceRouteDestination
aux := &struct {
RequestTimeout string
IdleTimeout string
*Alias
}{
Alias: (*Alias)(e),
@ -110,6 +117,11 @@ func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
return err
}
}
if aux.IdleTimeout != "" {
if e.IdleTimeout, err = time.ParseDuration(aux.IdleTimeout); err != nil {
return err
}
}
return nil
}

View File

@ -612,6 +612,7 @@ func TestDecodeConfigEntry(t *testing.T) {
"Namespace": "leek",
"PrefixRewrite": "/alternate",
"RequestTimeout": "99s",
"IdleTimeout": "99s",
"NumRetries": 12345,
"RetryOnConnectFailure": true,
"RetryOnStatusCodes": [401, 209]
@ -696,6 +697,7 @@ func TestDecodeConfigEntry(t *testing.T) {
Namespace: "leek",
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
IdleTimeout: 99 * time.Second,
NumRetries: 12345,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{401, 209},

View File

@ -821,6 +821,7 @@ func TestParseConfigEntry(t *testing.T) {
partition = "chard"
prefix_rewrite = "/alternate"
request_timeout = "99s"
idle_timeout = "99s"
num_retries = 12345
retry_on_connect_failure = true
retry_on_status_codes = [401, 209]
@ -906,6 +907,7 @@ func TestParseConfigEntry(t *testing.T) {
Partition = "chard"
PrefixRewrite = "/alternate"
RequestTimeout = "99s"
IdleTimeout = "99s"
NumRetries = 12345
RetryOnConnectFailure = true
RetryOnStatusCodes = [401, 209]
@ -992,6 +994,7 @@ func TestParseConfigEntry(t *testing.T) {
"partition": "chard",
"prefix_rewrite": "/alternate",
"request_timeout": "99s",
"idle_timeout": "99s",
"num_retries": 12345,
"retry_on_connect_failure": true,
"retry_on_status_codes": [
@ -1085,6 +1088,7 @@ func TestParseConfigEntry(t *testing.T) {
"Partition": "chard",
"PrefixRewrite": "/alternate",
"RequestTimeout": "99s",
"IdleTimeout": "99s",
"NumRetries": 12345,
"RetryOnConnectFailure": true,
"RetryOnStatusCodes": [
@ -1177,6 +1181,7 @@ func TestParseConfigEntry(t *testing.T) {
Partition: "chard",
PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second,
IdleTimeout: 99 * time.Second,
NumRetries: 12345,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{401, 209},

View File

@ -696,6 +696,12 @@ spec:
description:
'The total amount of time permitted for the entire downstream request (and retries) to be processed.',
},
{
name: 'IdleTimeout',
type: 'duration: 0',
description:
'The total amount of time permitted for the request stream to be idle',
},
{
name: 'NumRetries',
type: 'int: 0',

View File

@ -330,6 +330,11 @@ defaults that are inherited by all services.
specified, inherits the Envoy default for route timeouts (15s). A value of 0 will
disable request timeouts.
- `local_idle_timeout_ms` - In milliseconds, the idle timeout for HTTP requests
to the local application instance. Applies to HTTP based protocols only. If not
specified, inherits the Envoy default for route idle timeouts (15s). A value of 0
disables request timeouts.
- `max_inbound_connections` - The maximum number of concurrent inbound connections
to the local application instance. If not specified, inherits the Envoy default (1024).