From 72db37f4c96864e0c1916190fcdfcccdf0c556c7 Mon Sep 17 00:00:00 2001 From: "nastassia.dailidava" Date: Fri, 12 Jul 2024 14:38:49 +0200 Subject: [PATCH 1/2] allegro-internal/flex-roadmap#687 Added http compression filter configuration --- CHANGELOG.md | 4 + docs/configuration.md | 152 ++++++++++-------- .../snapshot/SnapshotProperties.kt | 21 +++ .../filters/CompressionFilterFactory.kt | 92 +++++++++++ .../listeners/filters/EnvoyDefaultFilters.kt | 17 +- .../envoycontrol/CompressionFilterTest.kt | 112 +++++++++++++ .../config/consul/ConsulExtension.kt | 5 +- .../config/envoy/EnvoyExtension.kt | 12 +- .../envoycontrol/EnvoyControlExtension.kt | 4 + .../config/service/EchoContainer.kt | 7 +- .../config/service/GenericServiceExtension.kt | 5 +- 11 files changed, 352 insertions(+), 79 deletions(-) create mode 100644 envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt create mode 100644 envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index cb20163d8..edc1118bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Lists all changes with user impact. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). +## [0.20.18] +### Changed +- Added http compression filter configuration + ## [0.20.17] ### Fixed - Fix JWT provider configuration to not impact lds cache diff --git a/docs/configuration.md b/docs/configuration.md index cbae56283..d63532964 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -22,76 +22,75 @@ Property **envoy-control.server.global-snapshot-audit-pool-size** | Pool size used for default global snapshot audit executor group | 10s ## Snapshot properties -Property | Description | Default value --------------------------------------------------------------------------------------------------------------| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- -**envoy-control.envoy.snapshot.dynamic-listeners.enabled** | Enable or disable creating listeners using dynamic configuration | true -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.enabled** | Enable or disable access logs | false -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.time-format** | Time format for access logs | "%START_TIME(%FT%T.%3fZ)%" -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.message-format** | Message format for access logs | "%PROTOCOL% %REQ(:METHOD)% %REQ(:authority)% %REQ(:PATH)% %DOWNSTREAM_REMOTE_ADDRESS% -> %UPSTREAM_HOST%" -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.level** | Logging level for access logs | "TRACE" -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.logger** | Logger name for access logs | "envoy.AccessLog" -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.custom-fields** | Custom fields, which should be included in access logs | null -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.status-code** | Default status code filter for access logs | null -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.duration** | Default duration filter for access logs | null -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.not-health-check** | Disable health checks filter for access logs | true -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.response-flag** | Default response flag filter for access logs | null -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.header** | Default header filter for access logs | null -**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.ingress-xff-num-trusted-hops** | Number of trusted hops for ingress filter (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html?highlight=xff_num_trusted_hops#x-forwarded-for)) | 1 -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.enabled** | Enable or disable creating local reply mapper configuration (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/local_reply)) | false -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.response-format.text-format** | Text message format with placeholders (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators)) | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.response-format.json-format** | JSON message format with placeholders for matched response (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators)). | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.response-format.content-type** | Response content-type header value | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.status-code-matcher** | Matcher which handles specific status codes formatted as string e.g.: EQ:400 - equal to status code 400 | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.header-matcher.name** | Header name to match | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.header-matcher.exact-match** | Header value to match for specified header (only one of: exactMatch, regexMatch can be specified. If none is specified, header name presence matcher will be used) | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.header-matcher.regex-match** | Header value regex to match for specified header (only one of: exactMatch, regexMatch can be specified. If none is specified, header name presence matcher will be used) | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-flag-matcher** | Response flags to match (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators)) | empty list -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.status-code-to-return** | Status code to return for matched response | 0 (disabled) -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.body-to-return** | Response message to return for matched response | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-format.text-format** | Text message format with placeholders for matched response | "" -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-format.json-format** | JSON message format with placeholders for matched response | empty map -**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-format.content-type** | Response content-type header value | "" -**envoy-control.envoy.snapshot.eds-connection-timeout** | Connection timeout for EDS clusters | 2s -**envoy-control.envoy.snapshot.egress.common-http.idle-timeout** | Set idle timeout for all HTTP connections (HTTP/1 and HTTP/2) | 120s -**envoy-control.envoy.snapshot.egress.common-http.request-timeout** | Set request timeout for all routes (HTTP/1 and HTTP/2) | 120s -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-connections** | The maximum number of connections that Envoy will make to the upstream cluster for high priority threshold. | 1024 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-pending-requests** | The maximum number of pending requests that Envoy will allow to the upstream cluster for high priority threshold. | 1024 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-requests** | The maximum number of parallel requests that Envoy will make to the upstream cluster for high priority threshold. | 1024 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-retries** | The maximum number of parallel retries that Envoy will allow to the upstream cluster for high priority threshold. | 3 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-connections** | The maximum number of connections that Envoy will make to the upstream cluster for default priority threshold. | 1024 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-pending-requests** | The maximum number of pending requests that Envoy will allow to the upstream cluster for default priority threshold. | 1024 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-requests** | The maximum number of parallel requests that Envoy will make to the upstream cluster for default priority threshold. | 1024 -**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-retries** | The maximum number of parallel retries that Envoy will allow to the upstream cluster for default priority threshold. | 3 -**envoy-control.envoy.snapshot.egress.never-remove-clusters** | Don't remove cluster, when corresponding service disappears from services source. Only remove all instances. | true -**envoy-control.envoy.snapshot.egress.cluster-not-found-status-code** | Status code when cluster is not found | 503 -**envoy-control.envoy.snapshot.egress.http2.enabled** | Enable http2 for clusters that use envoy | true -**envoy-control.envoy.snapshot.egress.http2.tag-name** | Tag to be used to identify if instance uses envoy | envoy -**envoy-control.envoy.snapshot.egress.handle-internal-redirect** | Handle redirects by Envoy | false -**envoy-control.envoy.snapshot.egress.host-header-rewriting.enabled** | Enable rewriting Host header with value from specified header | false -**envoy-control.envoy.snapshot.egress.host-header-rewriting.custom-host-header** | Header name which value will override Host header | "x-envoy-original-host" -**envoy-control.envoy.snapshot.egress.headers-to-remove** | List of headers to sanitize on egress | empty list -**envoy-control.envoy.snapshot.egress.domains** | List of domains added to service names for matching. Domain name has to start with '.' ( e.g.: .domain) | empty list -**envoy-control.envoy.snapshot.ingress.headers-to-remove** | List of headers to sanitize on ingress | empty list -**envoy-control.envoy.snapshot.local-service.idle-timeout** | Idle timeout between client to envoy | 60s -**envoy-control.envoy.snapshot.local-service.response-timeout** | Response timeout for localService | 15s -**envoy-control.envoy.snapshot.local-service.connection-idle-timeout** | Connection idle timeout for localService | 120s -**envoy-control.envoy.snapshot.routes.status.enabled** | Enable status route | false -**envoy-control.envoy.snapshot.routes.status.endpoints** | List of endpoints with path or prefix of status routes | /status -**envoy-control.envoy.snapshot.routes.status.create-virtual-cluster** | Create virtual cluster for status route | false -**envoy-control.envoy.snapshot.state-sample-duration** | Duration of state sampling (this is used to prevent surges in consul events overloading control plane) | 1s -**envoy-control.envoy.snapshot.xds-cluster-name** | Name of cluster for xDS operations | envoy-control-xds -**envoy-control.envoy.snapshot.enabled-communication-modes.ads** | Enable or disable support for ADS communication mode | true -**envoy-control.envoy.snapshot.enabled-communication-modes.xds** | Enable or disable support for XDS communication mode | true -**envoy-control.envoy.snapshot.should-send-missing-endpoints** | Enable sending missing Endpoints - when Envoy requests for not existing cluster in snapshot control-plane will respond with empty Endpoint definition | false -**envoy-control.envoy.snapshot.cluster-name** | Dynamic forward proxy cluster name | dynamic_forward_proxy_cluster -**envoy-control.envoy.snapshot.dns-lookup-family** | DNS lookup address family | V4_ONLY -**envoy-control.envoy.snapshot.max-cached-hosts** | The maximum number of hosts that the cache will hold | 1024 -**envoy-control.envoy.snapshot.max-host-ttl** | The TTL for hosts that are unused. Hosts that have not been used in the configured time interval will be purged | 300s -**envoy-control.envoy.snapshot.rate-limit.domain** | Domain name for ratelimit service. | rl -**envoy-control.envoy.snapshot.rate-limit.service-name** | ratelimit GRPC service name | ratelimit-grpc -**envoy-control.envoy.snapshot.delta-xds-enabled** | Enable detla xds | false -**envoy-control.envoy.snapshot.should-audit-global-snapshot** | Enable global snapshot audits | false - +Property | Description | Default value +-------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --------- +**envoy-control.envoy.snapshot.dynamic-listeners.enabled** | Enable or disable creating listeners using dynamic configuration | true +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.enabled** | Enable or disable access logs | false +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.time-format** | Time format for access logs | "%START_TIME(%FT%T.%3fZ)%" +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.message-format** | Message format for access logs | "%PROTOCOL% %REQ(:METHOD)% %REQ(:authority)% %REQ(:PATH)% %DOWNSTREAM_REMOTE_ADDRESS% -> %UPSTREAM_HOST%" +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.level** | Logging level for access logs | "TRACE" +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.logger** | Logger name for access logs | "envoy.AccessLog" +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.custom-fields** | Custom fields, which should be included in access logs | null +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.status-code** | Default status code filter for access logs | null +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.duration** | Default duration filter for access logs | null +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.not-health-check** | Disable health checks filter for access logs | true +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.response-flag** | Default response flag filter for access logs | null +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.access-log.filters.header** | Default header filter for access logs | null +**envoy-control.envoy.snapshot.dynamic-listeners.http-filters.ingress-xff-num-trusted-hops** | Number of trusted hops for ingress filter (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html?highlight=xff_num_trusted_hops#x-forwarded-for)) | 1 +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.enabled** | Enable or disable creating local reply mapper configuration (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/local_reply)) | false +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.response-format.text-format** | Text message format with placeholders (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators)) | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.response-format.json-format** | JSON message format with placeholders for matched response (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators)). | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.response-format.content-type** | Response content-type header value | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.status-code-matcher** | Matcher which handles specific status codes formatted as string e.g.: EQ:400 - equal to status code 400 | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.header-matcher.name** | Header name to match | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.header-matcher.exact-match** | Header value to match for specified header (only one of: exactMatch, regexMatch can be specified. If none is specified, header name presence matcher will be used) | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.header-matcher.regex-match** | Header value regex to match for specified header (only one of: exactMatch, regexMatch can be specified. If none is specified, header name presence matcher will be used) | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-flag-matcher** | Response flags to match (refer to [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators)) | empty list +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.status-code-to-return** | Status code to return for matched response | 0 (disabled) +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.body-to-return** | Response message to return for matched response | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-format.text-format** | Text message format with placeholders for matched response | "" +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-format.json-format** | JSON message format with placeholders for matched response | empty map +**envoy-control.envoy.snapshot.dynamic-listeners.local-reply-mapper.matchers.response-format.content-type** | Response content-type header value | "" +**envoy-control.envoy.snapshot.eds-connection-timeout** | Connection timeout for EDS clusters | 2s +**envoy-control.envoy.snapshot.egress.common-http.idle-timeout** | Set idle timeout for all HTTP connections (HTTP/1 and HTTP/2) | 120s +**envoy-control.envoy.snapshot.egress.common-http.request-timeout** | Set request timeout for all routes (HTTP/1 and HTTP/2) | 120s +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-connections** | The maximum number of connections that Envoy will make to the upstream cluster for high priority threshold. | 1024 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-pending-requests** | The maximum number of pending requests that Envoy will allow to the upstream cluster for high priority threshold. | 1024 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-requests** | The maximum number of parallel requests that Envoy will make to the upstream cluster for high priority threshold. | 1024 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.high-threshold.max-retries** | The maximum number of parallel retries that Envoy will allow to the upstream cluster for high priority threshold. | 3 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-connections** | The maximum number of connections that Envoy will make to the upstream cluster for default priority threshold. | 1024 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-pending-requests** | The maximum number of pending requests that Envoy will allow to the upstream cluster for default priority threshold. | 1024 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-requests** | The maximum number of parallel requests that Envoy will make to the upstream cluster for default priority threshold. | 1024 +**envoy-control.envoy.snapshot.egress.common-http.circuit-breakers.default-threshold.max-retries** | The maximum number of parallel retries that Envoy will allow to the upstream cluster for default priority threshold. | 3 +**envoy-control.envoy.snapshot.egress.never-remove-clusters** | Don't remove cluster, when corresponding service disappears from services source. Only remove all instances. | true +**envoy-control.envoy.snapshot.egress.cluster-not-found-status-code** | Status code when cluster is not found | 503 +**envoy-control.envoy.snapshot.egress.http2.enabled** | Enable http2 for clusters that use envoy | true +**envoy-control.envoy.snapshot.egress.http2.tag-name** | Tag to be used to identify if instance uses envoy | envoy +**envoy-control.envoy.snapshot.egress.handle-internal-redirect** | Handle redirects by Envoy | false +**envoy-control.envoy.snapshot.egress.host-header-rewriting.enabled** | Enable rewriting Host header with value from specified header | false +**envoy-control.envoy.snapshot.egress.host-header-rewriting.custom-host-header** | Header name which value will override Host header | "x-envoy-original-host" +**envoy-control.envoy.snapshot.egress.headers-to-remove** | List of headers to sanitize on egress | empty list +**envoy-control.envoy.snapshot.egress.domains** | List of domains added to service names for matching. Domain name has to start with '.' ( e.g.: .domain) | empty list +**envoy-control.envoy.snapshot.ingress.headers-to-remove** | List of headers to sanitize on ingress | empty list +**envoy-control.envoy.snapshot.local-service.idle-timeout** | Idle timeout between client to envoy | 60s +**envoy-control.envoy.snapshot.local-service.response-timeout** | Response timeout for localService | 15s +**envoy-control.envoy.snapshot.local-service.connection-idle-timeout** | Connection idle timeout for localService | 120s +**envoy-control.envoy.snapshot.routes.status.enabled** | Enable status route | false +**envoy-control.envoy.snapshot.routes.status.endpoints** | List of endpoints with path or prefix of status routes | /status +**envoy-control.envoy.snapshot.routes.status.create-virtual-cluster** | Create virtual cluster for status route | false +**envoy-control.envoy.snapshot.state-sample-duration** | Duration of state sampling (this is used to prevent surges in consul events overloading control plane) | 1s +**envoy-control.envoy.snapshot.xds-cluster-name** | Name of cluster for xDS operations | envoy-control-xds +**envoy-control.envoy.snapshot.enabled-communication-modes.ads** | Enable or disable support for ADS communication mode | true +**envoy-control.envoy.snapshot.enabled-communication-modes.xds** | Enable or disable support for XDS communication mode | true +**envoy-control.envoy.snapshot.should-send-missing-endpoints** | Enable sending missing Endpoints - when Envoy requests for not existing cluster in snapshot control-plane will respond with empty Endpoint definition | false +**envoy-control.envoy.snapshot.cluster-name** | Dynamic forward proxy cluster name | dynamic_forward_proxy_cluster +**envoy-control.envoy.snapshot.dns-lookup-family** | DNS lookup address family | V4_ONLY +**envoy-control.envoy.snapshot.max-cached-hosts** | The maximum number of hosts that the cache will hold | 1024 +**envoy-control.envoy.snapshot.max-host-ttl** | The TTL for hosts that are unused. Hosts that have not been used in the configured time interval will be purged | 300s +**envoy-control.envoy.snapshot.rate-limit.domain** | Domain name for ratelimit service. | rl +**envoy-control.envoy.snapshot.rate-limit.service-name** | ratelimit GRPC service name | ratelimit-grpc +**envoy-control.envoy.snapshot.delta-xds-enabled** | Enable detla xds | false +**envoy-control.envoy.snapshot.should-audit-global-snapshot** | Enable global snapshot audits | false ## Permissions Property | Description | Default value @@ -245,3 +244,16 @@ Property **envoy-control.envoy.snapshot.jwt.providers.{providerName}.cacheDuration** | Duration of caching public key fetched from provider | 300s **envoy-control.envoy.snapshot.jwt.providers.{providerName}.connectionTimeout** | Connection timeout for request fetching JWKs | 1s **envoy-control.envoy.snapshot.jwt.providers.{providerName}.matchings.{matching}** | Name of the token field that should be verified for given selector | empty map + +## Compression +Property | Description | Default value +--------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------- +**envoy-control.envoy.snapshot.compression.gzip.enabled** | Enable gzip compression | false +**envoy-control.envoy.snapshot.compression.gzip.chooseFirst** | Set gzip as the most prioritized compression algorithm | false +**envoy-control.envoy.snapshot.compression.brotli.enabled** | Enable brotli compression | false +**envoy-control.envoy.snapshot.compression.brotli.quality** | Compression quality | 11 +**envoy-control.envoy.snapshot.compression.brotli.chooseFirst** | Set brotli as the most prioritized compression algorithm | true +**envoy-control.envoy.snapshot.compression.minContentLength** | Minimum content lenght for enabling compression | 100 +**envoy-control.envoy.snapshot.compression.disableOnEtagHeader** | Disable compression if etag header is present | true +**envoy-control.envoy.snapshot.compression.requestCompressionEnabled** | Enable request compression | false +**envoy-control.envoy.snapshot.compression.responseCompressionEnabled** | Enable response compression | true diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt index b9142db8f..2428d9115 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt @@ -38,6 +38,7 @@ class SnapshotProperties { var retryPolicy = RetryPolicyProperties() var tcpDumpsEnabled: Boolean = true var shouldAuditGlobalSnapshot: Boolean = true + var compression: CompressionProperties = CompressionProperties() } class PathNormalizationProperties { @@ -377,6 +378,26 @@ class DynamicForwardProxyProperties { var connectionTimeout = Duration.ofSeconds(1) } +class CompressionProperties { + var gzip = GzipProperties() + var brotli = BrotliProperties() + var minContentLength = 100 + var disableOnEtagHeader = true + var requestCompressionEnabled = false + var responseCompressionEnabled = false +} + +class BrotliProperties { + var enabled = false + var quality = 11 + var chooseFirst = true +} + +class GzipProperties { + var enabled = false + var chooseFirst = false +} + data class OAuthProvider( var jwksUri: URI = URI.create("http://localhost"), var createCluster: Boolean = false, diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt new file mode 100644 index 000000000..8519b98ff --- /dev/null +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt @@ -0,0 +1,92 @@ +package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.listeners.filters + +import com.google.protobuf.BoolValue +import com.google.protobuf.UInt32Value +import io.envoyproxy.envoy.config.core.v3.RuntimeFeatureFlag +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig +import io.envoyproxy.envoy.config.filter.http.gzip.v2.Gzip.CompressionLevel.Enum.BEST_VALUE +import io.envoyproxy.envoy.extensions.compression.brotli.compressor.v3.Brotli +import io.envoyproxy.envoy.extensions.compression.gzip.compressor.v3.Gzip +import io.envoyproxy.envoy.extensions.filters.http.compressor.v3.Compressor +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter +import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties + +class CompressionFilterFactory(val properties: SnapshotProperties) { + + private val brotliCompressionFilter: HttpFilter = compressionFilter( + TypedExtensionConfig.newBuilder() + .setName("envoy.compression.brotli.compressor") + .setTypedConfig( + com.google.protobuf.Any.pack( + Brotli.newBuilder() + .setQuality(UInt32Value.of(properties.compression.brotli.quality)) + .build() + ) + ), + properties.compression.brotli.chooseFirst + ) + + private val gzipCompressionFilter: HttpFilter = compressionFilter( + TypedExtensionConfig.newBuilder() + .setName("envoy.compression.gzip.compressor") + .setTypedConfig( + com.google.protobuf.Any.pack( + Gzip.newBuilder() + .setCompressionStrategy(Gzip.CompressionStrategy.DEFAULT_STRATEGY) + .setCompressionLevel(Gzip.CompressionLevel.forNumber(BEST_VALUE)) + .build() + ) + ), + properties.compression.gzip.chooseFirst + ) + + fun gzipCompressionFilter(): HttpFilter? { + return if (properties.compression.gzip.enabled) { + gzipCompressionFilter + } else null + } + + fun brotliCompressionFilter(): HttpFilter? { + return if (properties.compression.brotli.enabled) { + brotliCompressionFilter + } else null + } + + private fun compressionFilter(library: TypedExtensionConfig.Builder, chooseFirst: Boolean) = + HttpFilter.newBuilder() + .setName("envoy.filters.http.compressor") + .setTypedConfig( + com.google.protobuf.Any.pack( + Compressor.newBuilder() + .setChooseFirst(chooseFirst) + .setRequestDirectionConfig( + Compressor.RequestDirectionConfig.newBuilder() + .setCommonConfig( + commonDirectionConfig( + "request_compressor_enabled", + properties.compression.requestCompressionEnabled + ) + ) + ).setResponseDirectionConfig( + Compressor.ResponseDirectionConfig.newBuilder() + .setCommonConfig( + commonDirectionConfig( + "response_compressor_enabled", + properties.compression.responseCompressionEnabled + ) + ) + .setDisableOnEtagHeader(properties.compression.disableOnEtagHeader) + ) + .setCompressorLibrary(library) + .build() + ) + ).build() + + private fun commonDirectionConfig(runtimeKey: String, defaultValue: Boolean) = + Compressor.CommonDirectionConfig.newBuilder() + .setEnabled( + RuntimeFeatureFlag.newBuilder().setRuntimeKey(runtimeKey) + .setDefaultValue(BoolValue.of(defaultValue)) + ) + .setMinContentLength(UInt32Value.of(properties.compression.minContentLength)) +} diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt index e4475043d..7a49d2531 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt @@ -28,6 +28,8 @@ class EnvoyDefaultFilters( properties = snapshotProperties.routing.serviceTags ) + private val compressionFilterFactory = CompressionFilterFactory(snapshotProperties) + private val defaultServiceTagHeaderToMetadataFilterRules = serviceTagFilterFactory.headerToMetadataFilterRules() private val defaultHeaderToMetadataConfig = headerToMetadataConfig(defaultServiceTagHeaderToMetadataFilterRules) private val headerToMetadataHttpFilter = headerToMetadataHttpFilter(defaultHeaderToMetadataConfig) @@ -62,8 +64,21 @@ class EnvoyDefaultFilters( val defaultAuthorizationHeaderFilter = { _: Group, _: GlobalSnapshot -> authorizationHeaderToMetadataFilter() } + + val defaultGzipCompressionFilter = { _: Group, _: GlobalSnapshot -> + compressionFilterFactory.gzipCompressionFilter() + } + + val defaultBrotliCompressionFilter = { _: Group, _: GlobalSnapshot -> + compressionFilterFactory.brotliCompressionFilter() + } + val defaultEgressFilters = listOf( - defaultHeaderToMetadataFilter, defaultServiceTagFilter, defaultEnvoyRouterHttpFilter + defaultHeaderToMetadataFilter, + defaultServiceTagFilter, + defaultGzipCompressionFilter, + defaultBrotliCompressionFilter, + defaultEnvoyRouterHttpFilter, ) val defaultCurrentZoneHeaderFilter = { _: Group, _: GlobalSnapshot -> diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt new file mode 100644 index 000000000..a7c900f6b --- /dev/null +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt @@ -0,0 +1,112 @@ +package pl.allegro.tech.servicemesh.envoycontrol + +import okhttp3.Response +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.ObjectAssert +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import pl.allegro.tech.servicemesh.envoycontrol.assertions.untilAsserted +import pl.allegro.tech.servicemesh.envoycontrol.config.Xds +import pl.allegro.tech.servicemesh.envoycontrol.config.consul.ConsulExtension +import pl.allegro.tech.servicemesh.envoycontrol.config.envoy.EnvoyExtension +import pl.allegro.tech.servicemesh.envoycontrol.config.envoycontrol.EnvoyControlExtension +import pl.allegro.tech.servicemesh.envoycontrol.config.service.EchoContainer +import pl.allegro.tech.servicemesh.envoycontrol.config.service.EchoServiceExtension +import pl.allegro.tech.servicemesh.envoycontrol.config.service.GenericServiceExtension + +class CompressionFilterTest { + + companion object { + + @JvmField + @RegisterExtension + val consul = ConsulExtension() + + @JvmField + @RegisterExtension + val envoyControl = EnvoyControlExtension( + consul, + mapOf( + "envoy-control.envoy.snapshot.compression.gzip.enabled" to true, + "envoy-control.envoy.snapshot.compression.brotli.enabled" to true, + "envoy-control.envoy.snapshot.compression.minContentLength" to 100, + "envoy-control.envoy.snapshot.compression.responseCompressionEnabled" to true, + ) + ) + private const val SERVICE_NAME = "service-1" + private const val LONG_STRING = "Workshallmeantheworkofauthorship,whetherinSourceorObjectform," + + "madeavailableundertheLicensesindicatedbyacopyrightnoticethatisincludedinorattachedto" + + "thework(anexampleisprovidedintheAppendixbelow)." + + private val serviceConfig = """ + node: + metadata: + proxy_settings: + incoming: + unlistedEndpointsPolicy: log + endpoints: [] + """.trimIndent() + private val config = Xds.copy(configOverride = serviceConfig, serviceName = SERVICE_NAME) + private val longText = LONG_STRING.repeat(100) + + @JvmField + @RegisterExtension + val service = + GenericServiceExtension(EchoContainer(longText)) + + @JvmField + @RegisterExtension + val downstreamService = EchoServiceExtension() + + @JvmField + @RegisterExtension + val downstreamEnvoy = EnvoyExtension(envoyControl, downstreamService, config = Xds) + + @JvmField + @RegisterExtension + val serviceEnvoy = EnvoyExtension(envoyControl, config = config, localService = service) + } + + @Test + fun `should compress response with brotli`() { + consul.server.operations.registerServiceWithEnvoyOnIngress(serviceEnvoy, name = SERVICE_NAME) + downstreamEnvoy.waitForReadyServices(SERVICE_NAME) + untilAsserted { + val response = + downstreamEnvoy.egressOperations.callService(SERVICE_NAME, headers = mapOf("accept-encoding" to "br")) + assertThat(response).isCompressedWith("br") + } + } + + @Test + fun `should compress response with gzip`() { + consul.server.operations.registerServiceWithEnvoyOnIngress(serviceEnvoy, name = SERVICE_NAME) + downstreamEnvoy.waitForReadyServices(SERVICE_NAME) + untilAsserted { + val response = + downstreamEnvoy.egressOperations.callService(SERVICE_NAME, headers = mapOf("accept-encoding" to "gzip")) + assertThat(response).isCompressedWith("gzip") + } + } + + @Test + fun `should not compress response when accept encoding header is missing`() { + consul.server.operations.registerServiceWithEnvoyOnIngress(serviceEnvoy, name = SERVICE_NAME) + downstreamEnvoy.waitForReadyServices(SERVICE_NAME) + untilAsserted { + val response = + downstreamEnvoy.egressOperations.callService(SERVICE_NAME) + assertThat(response).isNotCompressed() + } + } + + private fun ObjectAssert.isCompressedWith(encoding: String): ObjectAssert { + matches { it.isSuccessful && it.headers.any { x -> x.first == "content-encoding" && x.second == encoding } } + return this + } + + private fun ObjectAssert.isNotCompressed(): ObjectAssert { + matches { it.isSuccessful && it.headers.none { x -> x.first == "content-encoding" } } + return this + } +} diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/consul/ConsulExtension.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/consul/ConsulExtension.kt index fdf8ddf18..e95caba8b 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/consul/ConsulExtension.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/consul/ConsulExtension.kt @@ -5,6 +5,7 @@ import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.ExtensionContext import org.testcontainers.containers.Network +import pl.allegro.tech.servicemesh.envoycontrol.logger class ConsulExtension : BeforeAllCallback, AfterAllCallback, AfterEachCallback { @@ -16,15 +17,17 @@ class ConsulExtension : BeforeAllCallback, AfterAllCallback, AfterEachCallback { } val server = SHARED_CONSUL + private val logger by logger() private var started = false override fun beforeAll(context: ExtensionContext) { if (started) { return } - + logger.info("Consul extension is starting.") server.container.start() started = true + logger.info("Consul extension started.") } override fun afterEach(context: ExtensionContext) { diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoy/EnvoyExtension.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoy/EnvoyExtension.kt index a3ce75fc3..f80f5e77a 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoy/EnvoyExtension.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoy/EnvoyExtension.kt @@ -1,6 +1,7 @@ package pl.allegro.tech.servicemesh.envoycontrol.config.envoy import org.assertj.core.api.Assertions.assertThat +import org.awaitility.Awaitility import org.junit.jupiter.api.extension.AfterAllCallback import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.BeforeAllCallback @@ -15,11 +16,12 @@ import pl.allegro.tech.servicemesh.envoycontrol.config.envoycontrol.EnvoyControl import pl.allegro.tech.servicemesh.envoycontrol.config.service.ServiceExtension import pl.allegro.tech.servicemesh.envoycontrol.logger import java.time.Duration +import java.util.concurrent.TimeUnit class EnvoyExtension( private val envoyControl: EnvoyControlExtensionBase, private val localService: ServiceExtension<*>? = null, - private val config: EnvoyConfig = RandomConfigFile + config: EnvoyConfig = RandomConfigFile ) : BeforeAllCallback, AfterAllCallback, AfterEachCallback { companion object { @@ -38,15 +40,21 @@ class EnvoyExtension( override fun beforeAll(context: ExtensionContext) { localService?.beforeAll(context) envoyControl.beforeAll(context) - try { container.start() + waitUntilHealthy() } catch (e: Exception) { logger.error("Logs from failed container: ${container.logs}") throw e } } + private fun waitUntilHealthy() { + Awaitility.await().atMost(1, TimeUnit.MINUTES).untilAsserted { + assertThat(container.admin().isIngressReady()) + } + } + override fun afterAll(context: ExtensionContext) { container.stop() } diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoycontrol/EnvoyControlExtension.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoycontrol/EnvoyControlExtension.kt index 18872786d..a7e017587 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoycontrol/EnvoyControlExtension.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/envoycontrol/EnvoyControlExtension.kt @@ -6,6 +6,7 @@ import org.junit.jupiter.api.extension.AfterAllCallback import org.junit.jupiter.api.extension.BeforeAllCallback import org.junit.jupiter.api.extension.ExtensionContext import pl.allegro.tech.servicemesh.envoycontrol.config.consul.ConsulExtension +import pl.allegro.tech.servicemesh.envoycontrol.logger import java.util.concurrent.TimeUnit interface EnvoyControlExtensionBase : BeforeAllCallback, AfterAllCallback { @@ -16,6 +17,7 @@ class EnvoyControlExtension(private val consul: ConsulExtension, override val ap : EnvoyControlExtensionBase { private var started = false + private val logger by logger() constructor(consul: ConsulExtension, properties: Map = mapOf()) : this(consul, EnvoyControlRunnerTestApp( @@ -29,9 +31,11 @@ class EnvoyControlExtension(private val consul: ConsulExtension, override val ap } consul.beforeAll(context) + logger.info("Envoy control extension is starting.") app.run() waitUntilHealthy() started = true + logger.info("Envoy control extension started.") } private fun waitUntilHealthy() { diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/EchoContainer.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/EchoContainer.kt index c704ad8f0..58710e6a8 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/EchoContainer.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/EchoContainer.kt @@ -3,12 +3,11 @@ package pl.allegro.tech.servicemesh.envoycontrol.config.service import org.testcontainers.containers.Network import org.testcontainers.containers.wait.strategy.Wait import pl.allegro.tech.servicemesh.envoycontrol.config.testcontainers.GenericContainer -import java.util.UUID import java.util.Locale +import java.util.UUID -class EchoContainer : GenericContainer("jxlwqq/http-echo"), ServiceContainer { - - val response = UUID.randomUUID().toString() +class EchoContainer(val response: String = UUID.randomUUID().toString()) : + GenericContainer("jxlwqq/http-echo"), ServiceContainer { override fun configure() { super.configure() diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/GenericServiceExtension.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/GenericServiceExtension.kt index 50d5fb7b2..c54a230b1 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/GenericServiceExtension.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/config/service/GenericServiceExtension.kt @@ -1,9 +1,11 @@ package pl.allegro.tech.servicemesh.envoycontrol.config.service import org.junit.jupiter.api.extension.ExtensionContext +import pl.allegro.tech.servicemesh.envoycontrol.logger class GenericServiceExtension(private val container: T) : ServiceExtension { + private val logger by logger() private var started = false override fun container() = container @@ -12,9 +14,10 @@ class GenericServiceExtension(private val container: T) : if (started) { return } - + logger.info("Generic service is starting.") container.start() started = true + logger.info("Generic service extension started.") } override fun afterAll(context: ExtensionContext) { From 52365b157bb418810b6fc2fb65dfb08a39319330 Mon Sep 17 00:00:00 2001 From: "nastassia.dailidava" Date: Thu, 25 Jul 2024 13:23:50 +0200 Subject: [PATCH 2/2] allegro-internal/flex-roadmap#687 Added http compression filter configuration (added a white list of services) --- .../snapshot/SnapshotProperties.kt | 5 +- .../filters/CompressionFilterFactory.kt | 11 ++-- .../listeners/filters/EnvoyDefaultFilters.kt | 8 +-- .../envoycontrol/CompressionFilterTest.kt | 57 +++++++++++++------ 4 files changed, 56 insertions(+), 25 deletions(-) diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt index 2428d9115..6c90d2e1d 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/SnapshotProperties.kt @@ -46,6 +46,7 @@ class PathNormalizationProperties { var mergeSlashes = true var pathWithEscapedSlashesAction = "KEEP_UNCHANGED" } + class MetricsProperties { var cacheSetSnapshot = false } @@ -65,7 +66,7 @@ class AccessLogProperties { var enabled = false var timeFormat = "%START_TIME(%FT%T.%3fZ)%" var messageFormat = "%PROTOCOL% %REQ(:METHOD)% %REQ(:authority)% %REQ(:PATH)% " + - "%DOWNSTREAM_REMOTE_ADDRESS% -> %UPSTREAM_HOST%" + "%DOWNSTREAM_REMOTE_ADDRESS% -> %UPSTREAM_HOST%" var level = "TRACE" var logger = "envoy.AccessLog" var customFields = mapOf() @@ -131,6 +132,7 @@ class ClientsListsProperties { var defaultClientsList: List = emptyList() var customClientsLists: Map> = mapOf() } + class TlsProtocolProperties { var cipherSuites: List = listOf("ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-GCM-SHA256") var minimumVersion = TlsParameters.TlsProtocol.TLSv1_2 @@ -385,6 +387,7 @@ class CompressionProperties { var disableOnEtagHeader = true var requestCompressionEnabled = false var responseCompressionEnabled = false + var enableForServices: List = emptyList() } class BrotliProperties { diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt index 8519b98ff..2f3cc32ef 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/CompressionFilterFactory.kt @@ -9,6 +9,7 @@ import io.envoyproxy.envoy.extensions.compression.brotli.compressor.v3.Brotli import io.envoyproxy.envoy.extensions.compression.gzip.compressor.v3.Gzip import io.envoyproxy.envoy.extensions.filters.http.compressor.v3.Compressor import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter +import pl.allegro.tech.servicemesh.envoycontrol.groups.Group import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotProperties class CompressionFilterFactory(val properties: SnapshotProperties) { @@ -40,14 +41,14 @@ class CompressionFilterFactory(val properties: SnapshotProperties) { properties.compression.gzip.chooseFirst ) - fun gzipCompressionFilter(): HttpFilter? { - return if (properties.compression.gzip.enabled) { + fun gzipCompressionFilter(group: Group): HttpFilter? { + return if (properties.compression.gzip.enabled && group.hasCompressionEnabled()) { gzipCompressionFilter } else null } - fun brotliCompressionFilter(): HttpFilter? { - return if (properties.compression.brotli.enabled) { + fun brotliCompressionFilter(group: Group): HttpFilter? { + return if (properties.compression.brotli.enabled && group.hasCompressionEnabled()) { brotliCompressionFilter } else null } @@ -89,4 +90,6 @@ class CompressionFilterFactory(val properties: SnapshotProperties) { .setDefaultValue(BoolValue.of(defaultValue)) ) .setMinContentLength(UInt32Value.of(properties.compression.minContentLength)) + + private fun Group.hasCompressionEnabled() = properties.compression.enableForServices.contains(this.serviceName) } diff --git a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt index 7a49d2531..07e92eb53 100644 --- a/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt +++ b/envoy-control-core/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/snapshot/resource/listeners/filters/EnvoyDefaultFilters.kt @@ -65,12 +65,12 @@ class EnvoyDefaultFilters( authorizationHeaderToMetadataFilter() } - val defaultGzipCompressionFilter = { _: Group, _: GlobalSnapshot -> - compressionFilterFactory.gzipCompressionFilter() + val defaultGzipCompressionFilter = { group: Group, _: GlobalSnapshot -> + compressionFilterFactory.gzipCompressionFilter(group) } - val defaultBrotliCompressionFilter = { _: Group, _: GlobalSnapshot -> - compressionFilterFactory.brotliCompressionFilter() + val defaultBrotliCompressionFilter = { group: Group, _: GlobalSnapshot -> + compressionFilterFactory.brotliCompressionFilter(group) } val defaultEgressFilters = listOf( diff --git a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt index a7c900f6b..36a3d19d7 100644 --- a/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt +++ b/envoy-control-tests/src/main/kotlin/pl/allegro/tech/servicemesh/envoycontrol/CompressionFilterTest.kt @@ -6,6 +6,7 @@ import org.assertj.core.api.ObjectAssert import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import pl.allegro.tech.servicemesh.envoycontrol.assertions.untilAsserted +import pl.allegro.tech.servicemesh.envoycontrol.config.AdsAllDependencies import pl.allegro.tech.servicemesh.envoycontrol.config.Xds import pl.allegro.tech.servicemesh.envoycontrol.config.consul.ConsulExtension import pl.allegro.tech.servicemesh.envoycontrol.config.envoy.EnvoyExtension @@ -17,6 +18,22 @@ import pl.allegro.tech.servicemesh.envoycontrol.config.service.GenericServiceExt class CompressionFilterTest { companion object { + private const val SERVICE_NAME = "service-1" + private const val DOWNSTREAM_SERVICE_NAME = "echo2" + private const val LONG_STRING = "Workshallmeantheworkofauthorship,whetherinSourceorObjectform," + + "madeavailableundertheLicensesindicatedbyacopyrightnoticethatisincludedinorattachedto" + + "thework(anexampleisprovidedintheAppendixbelow)." + private val serviceConfig = """ + node: + metadata: + proxy_settings: + incoming: + unlistedEndpointsPolicy: log + endpoints: [] + """.trimIndent() + private val config = Xds.copy(configOverride = serviceConfig, serviceName = SERVICE_NAME) + private val unListedServiceConfig = AdsAllDependencies + private val longText = LONG_STRING.repeat(100) @JvmField @RegisterExtension @@ -31,36 +48,32 @@ class CompressionFilterTest { "envoy-control.envoy.snapshot.compression.brotli.enabled" to true, "envoy-control.envoy.snapshot.compression.minContentLength" to 100, "envoy-control.envoy.snapshot.compression.responseCompressionEnabled" to true, + "envoy-control.envoy.snapshot.compression.enableForServices" to listOf(DOWNSTREAM_SERVICE_NAME), + "envoy-control.envoy.snapshot.outgoing-permissions.servicesAllowedToUseWildcard" to "test-service" + ) ) - private const val SERVICE_NAME = "service-1" - private const val LONG_STRING = "Workshallmeantheworkofauthorship,whetherinSourceorObjectform," + - "madeavailableundertheLicensesindicatedbyacopyrightnoticethatisincludedinorattachedto" + - "thework(anexampleisprovidedintheAppendixbelow)." - - private val serviceConfig = """ - node: - metadata: - proxy_settings: - incoming: - unlistedEndpointsPolicy: log - endpoints: [] - """.trimIndent() - private val config = Xds.copy(configOverride = serviceConfig, serviceName = SERVICE_NAME) - private val longText = LONG_STRING.repeat(100) @JvmField @RegisterExtension val service = GenericServiceExtension(EchoContainer(longText)) + @JvmField + @RegisterExtension + val noCompressionService = GenericServiceExtension(EchoContainer(longText)) + @JvmField @RegisterExtension val downstreamService = EchoServiceExtension() @JvmField @RegisterExtension - val downstreamEnvoy = EnvoyExtension(envoyControl, downstreamService, config = Xds) + val downstreamEnvoy = EnvoyExtension(envoyControl, downstreamService, Xds) + + @JvmField + @RegisterExtension + val noCompressionEnvoy = EnvoyExtension(envoyControl, noCompressionService, unListedServiceConfig) @JvmField @RegisterExtension @@ -100,6 +113,18 @@ class CompressionFilterTest { } } + @Test + fun `should not enable compression on unlisted service`() { + consul.server.operations.registerServiceWithEnvoyOnIngress(serviceEnvoy, name = SERVICE_NAME) + noCompressionEnvoy.waitForReadyServices(SERVICE_NAME) + untilAsserted { + val response = + noCompressionEnvoy.egressOperations.callService(SERVICE_NAME, headers = mapOf("accept-encoding" to "gzip")) + println(response.headers.toString()) + assertThat(response).isNotCompressed() + } + } + private fun ObjectAssert.isCompressedWith(encoding: String): ObjectAssert { matches { it.isSuccessful && it.headers.any { x -> x.first == "content-encoding" && x.second == encoding } } return this