diff --git a/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto b/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto index 9f8666b6a4ad4..594aec339a5cf 100644 --- a/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto +++ b/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto @@ -17,6 +17,23 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Common rate limit components] +// Defines the version of the standard to use for X-RateLimit headers. +enum XRateLimitHeadersRFCVersion { + // X-RateLimit headers disabled. + OFF = 0; + + // Use `draft RFC Version 03 `_ where 3 headers will be added: + // + // * ``X-RateLimit-Limit`` - indicates the request-quota associated to the + // client in the current time-window followed by the description of the + // quota policy. The value is returned by the maximum tokens of the token bucket. + // * ``X-RateLimit-Remaining`` - indicates the remaining requests in the + // current time-window. The value is returned by the remaining tokens in the token bucket. + // * ``X-RateLimit-Reset`` - indicates the number of seconds until reset of + // the current time-window. The value is returned by the remaining fill interval of the token bucket. + DRAFT_VERSION_03 = 1; +} + // A RateLimitDescriptor is a list of hierarchical entries that are used by the service to // determine the final rate limit key and overall allowed limit. Here are some examples of how // they might be used for the domain "envoy". diff --git a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index 9d8252d9bc98a..ad98da0a50f0a 100644 --- a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -20,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Local Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.local_ratelimit] -// [#next-free-field: 12] +// [#next-free-field: 13] message LocalRateLimit { // The human readable prefix to use when emitting stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -107,4 +107,10 @@ message LocalRateLimit { // one to rate limit requests on a per connection basis. // If unspecified, the default value is false. bool local_rate_limit_per_downstream_connection = 11; + + // Defines the standard version to use for X-RateLimit headers emitted by the filter. + // + // Disabled by default. + common.ratelimit.v3.XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 12 + [(validate.rules).enum = {defined_only: true}]; } diff --git a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto index b76cbf4a4a9e3..a0b30661db45a 100644 --- a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto +++ b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto @@ -29,6 +29,8 @@ message RateLimit { "envoy.config.filter.http.rate_limit.v2.RateLimit"; // Defines the version of the standard to use for X-RateLimit headers. + // + // [#next-major-version: unify with local ratelimit, should use common.ratelimit.v3.XRateLimitHeadersRFCVersion instead.] enum XRateLimitHeadersRFCVersion { // X-RateLimit headers disabled. OFF = 0; @@ -100,6 +102,8 @@ message RateLimit { // the `draft RFC `_. // // Disabled by default. + // + // [#next-major-version: unify with local ratelimit, should use common.ratelimit.v3.XRateLimitHeadersRFCVersion instead.] XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 8 [(validate.rules).enum = {defined_only: true}]; diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3e585568071ba..b0edddb7408be 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -56,6 +56,7 @@ New Features * http: make consistent custom header format fields ``%(DOWN|DIRECT_DOWN|UP)STREAM_(LOCAL|REMOTE)_*%`` to provide all combinations of local & remote addresses for upstream & downstream connections. * http3: downstream HTTP/3 support is now GA! Upstream HTTP/3 also GA for specific deployments. See :ref:`here ` for details. * http3: supports upstream HTTP/3 retries. Automatically retry `0-RTT safe requests `_ if they are rejected because they are sent `too early `_. And automatically retry 0-RTT safe requests if connect attempt fails later on and the cluster is configured with TCP fallback. And add retry on ``http3-post-connect-failure`` policy which allows retry of failed HTTP/3 requests with TCP fallback even after handshake if the cluster is configured with TCP fallback. This feature is guarded by ``envoy.reloadable_features.conn_pool_new_stream_with_early_data_and_http3``. +* local_ratelimit: added support for X-RateLimit-* headers as defined in `draft RFC `_. * matching: the matching API can now express a match tree that will always match by omitting a matcher at the top level. Deprecated diff --git a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc index 1acd3049456cd..d52f710eb93d7 100644 --- a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc +++ b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc @@ -1,5 +1,7 @@ #include "source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h" +#include + #include "source/common/protobuf/utility.h" namespace Envoy { @@ -25,6 +27,7 @@ LocalRateLimiterImpl::LocalRateLimiterImpl( token_bucket_.tokens_per_fill_ = tokens_per_fill; token_bucket_.fill_interval_ = absl::FromChrono(fill_interval); tokens_.tokens_ = max_tokens; + tokens_.fill_time_ = time_source_.monotonicTime(); if (fill_timer_) { fill_timer_->enableTimer(fill_interval); @@ -72,7 +75,7 @@ void LocalRateLimiterImpl::onFillTimer() { fill_timer_->enableTimer(absl::ToChronoMilliseconds(token_bucket_.fill_interval_)); } -void LocalRateLimiterImpl::onFillTimerHelper(const TokenState& tokens, +void LocalRateLimiterImpl::onFillTimerHelper(TokenState& tokens, const RateLimit::TokenBucket& bucket) { // Relaxed consistency is used for all operations because we don't care about ordering, just the // final atomic correctness. @@ -88,6 +91,9 @@ void LocalRateLimiterImpl::onFillTimerHelper(const TokenState& tokens, // Loop while the weak CAS fails trying to update the tokens value. } while (!tokens.tokens_.compare_exchange_weak(expected_tokens, new_tokens_value, std::memory_order_relaxed)); + + // Update fill time at last. + tokens.fill_time_ = time_source_.monotonicTime(); } void LocalRateLimiterImpl::onFillTimerDescriptorHelper() { @@ -97,7 +103,6 @@ void LocalRateLimiterImpl::onFillTimerDescriptorHelper() { current_time - descriptor.token_state_->fill_time_) >= absl::ToChronoMilliseconds(descriptor.token_bucket_.fill_interval_)) { onFillTimerHelper(*descriptor.token_state_, descriptor.token_bucket_); - descriptor.token_state_->fill_time_ = current_time; } } } @@ -123,17 +128,63 @@ bool LocalRateLimiterImpl::requestAllowedHelper(const TokenState& tokens) const return true; } -bool LocalRateLimiterImpl::requestAllowed( +OptRef LocalRateLimiterImpl::descriptorHelper( absl::Span request_descriptors) const { if (!descriptors_.empty() && !request_descriptors.empty()) { + // The override rate limit descriptor is selected by the first full match from the request + // descriptors. for (const auto& request_descriptor : request_descriptors) { auto it = descriptors_.find(request_descriptor); if (it != descriptors_.end()) { - return requestAllowedHelper(*it->token_state_); + return *it; } } } - return requestAllowedHelper(tokens_); + return {}; +} + +bool LocalRateLimiterImpl::requestAllowed( + absl::Span request_descriptors) const { + auto descriptor = descriptorHelper(request_descriptors); + + return descriptor.has_value() ? requestAllowedHelper(*descriptor.value().get().token_state_) + : requestAllowedHelper(tokens_); +} + +uint32_t LocalRateLimiterImpl::maxTokens( + absl::Span request_descriptors) const { + auto descriptor = descriptorHelper(request_descriptors); + + return descriptor.has_value() ? descriptor.value().get().token_bucket_.max_tokens_ + : token_bucket_.max_tokens_; +} + +uint32_t LocalRateLimiterImpl::remainingTokens( + absl::Span request_descriptors) const { + auto descriptor = descriptorHelper(request_descriptors); + + return descriptor.has_value() + ? descriptor.value().get().token_state_->tokens_.load(std::memory_order_relaxed) + : tokens_.tokens_.load(std::memory_order_relaxed); +} + +int64_t LocalRateLimiterImpl::remainingFillInterval( + absl::Span request_descriptors) const { + using namespace std::literals; + + auto current_time = time_source_.monotonicTime(); + auto descriptor = descriptorHelper(request_descriptors); + // Remaining time to next fill = fill interval - (current time - last fill time). + if (descriptor.has_value()) { + ASSERT(std::chrono::duration_cast( + current_time - descriptor.value().get().token_state_->fill_time_) <= + absl::ToChronoMilliseconds(descriptor.value().get().token_bucket_.fill_interval_)); + return absl::ToInt64Seconds( + descriptor.value().get().token_bucket_.fill_interval_ - + absl::Seconds((current_time - descriptor.value().get().token_state_->fill_time_) / 1s)); + } + return absl::ToInt64Seconds(token_bucket_.fill_interval_ - + absl::Seconds((current_time - tokens_.fill_time_) / 1s)); } } // namespace LocalRateLimit diff --git a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h index ab8f4e3a5ea51..452b85747f705 100644 --- a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h +++ b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h @@ -26,6 +26,10 @@ class LocalRateLimiterImpl { ~LocalRateLimiterImpl(); bool requestAllowed(absl::Span request_descriptors) const; + uint32_t maxTokens(absl::Span request_descriptors) const; + uint32_t remainingTokens(absl::Span request_descriptors) const; + int64_t + remainingFillInterval(absl::Span request_descriptors) const; private: struct TokenState { @@ -59,8 +63,10 @@ class LocalRateLimiterImpl { }; void onFillTimer(); - void onFillTimerHelper(const TokenState& state, const RateLimit::TokenBucket& bucket); + void onFillTimerHelper(TokenState& state, const RateLimit::TokenBucket& bucket); void onFillTimerDescriptorHelper(); + OptRef + descriptorHelper(absl::Span request_descriptors) const; bool requestAllowedHelper(const TokenState& tokens) const; RateLimit::TokenBucket token_bucket_; diff --git a/source/extensions/filters/http/common/BUILD b/source/extensions/filters/http/common/BUILD index 140a0787a7492..7bc4eea7f456a 100644 --- a/source/extensions/filters/http/common/BUILD +++ b/source/extensions/filters/http/common/BUILD @@ -56,3 +56,11 @@ envoy_cc_library( "//source/common/common:token_bucket_impl_lib", ], ) + +envoy_cc_library( + name = "ratelimit_headers_lib", + hdrs = ["ratelimit_headers.h"], + deps = [ + "//source/common/http:header_map_lib", + ], +) diff --git a/source/extensions/filters/http/common/ratelimit_headers.h b/source/extensions/filters/http/common/ratelimit_headers.h new file mode 100644 index 0000000000000..f8c9372c9d325 --- /dev/null +++ b/source/extensions/filters/http/common/ratelimit_headers.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/http/header_map.h" + +#include "source/common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Common { +namespace RateLimit { + +class XRateLimitHeaderValues { +public: + const Http::LowerCaseString XRateLimitLimit{"x-ratelimit-limit"}; + const Http::LowerCaseString XRateLimitRemaining{"x-ratelimit-remaining"}; + const Http::LowerCaseString XRateLimitReset{"x-ratelimit-reset"}; + + struct { + const std::string Window{"w"}; + const std::string Name{"name"}; + } QuotaPolicyKeys; +}; + +using XRateLimitHeaders = ConstSingleton; +} // namespace RateLimit +} // namespace Common +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/local_ratelimit/BUILD b/source/extensions/filters/http/local_ratelimit/BUILD index 0f3949915f59b..88d4042e98097 100644 --- a/source/extensions/filters/http/local_ratelimit/BUILD +++ b/source/extensions/filters/http/local_ratelimit/BUILD @@ -28,6 +28,8 @@ envoy_cc_library( "//source/extensions/filters/common/local_ratelimit:local_ratelimit_lib", "//source/extensions/filters/common/ratelimit:ratelimit_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", + "//source/extensions/filters/http/common:ratelimit_headers_lib", + "@envoy_api//envoy/extensions/common/ratelimit/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/local_ratelimit/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index 5dc4a1c3286af..a4a5ee44fe06a 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -4,10 +4,13 @@ #include #include +#include "envoy/extensions/common/ratelimit/v3/ratelimit.pb.h" +#include "envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.pb.h" #include "envoy/http/codes.h" #include "source/common/http/utility.h" #include "source/common/router/config_impl.h" +#include "source/extensions/filters/http/common/ratelimit_headers.h" namespace Envoy { namespace Extensions { @@ -48,7 +51,9 @@ FilterConfig::FilterConfig( request_headers_parser_(Envoy::Router::HeaderParser::configure( config.request_headers_to_add_when_not_enforced())), stage_(static_cast(config.stage())), - has_descriptors_(!config.descriptors().empty()) { + has_descriptors_(!config.descriptors().empty()), + enable_x_rate_limit_headers_(config.enable_x_ratelimit_headers() == + envoy::extensions::common::ratelimit::v3::DRAFT_VERSION_03) { // Note: no token bucket is fine for the global config, which would be the case for enabling // the filter globally but disabled and then applying limits at the virtual host or // route level. At the virtual or route level, it makes no sense to have an no token @@ -64,6 +69,21 @@ bool FilterConfig::requestAllowed( return rate_limiter_.requestAllowed(request_descriptors); } +uint32_t +FilterConfig::maxTokens(absl::Span request_descriptors) const { + return rate_limiter_.maxTokens(request_descriptors); +} + +uint32_t FilterConfig::remainingTokens( + absl::Span request_descriptors) const { + return rate_limiter_.remainingTokens(request_descriptors); +} + +int64_t FilterConfig::remainingFillInterval( + absl::Span request_descriptors) const { + return rate_limiter_.remainingFillInterval(request_descriptors); +} + LocalRateLimitStats FilterConfig::generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = prefix + ".http_local_rate_limit"; return {ALL_LOCAL_RATE_LIMIT_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; @@ -91,6 +111,9 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, populateDescriptors(descriptors, headers); } + // Store descriptors which is used to generate x-ratelimit-* headers in encoding response headers. + stored_descriptors_ = descriptors; + if (requestAllowed(descriptors)) { config->stats().ok_.inc(); return Http::FilterHeadersStatus::Continue; @@ -116,6 +139,26 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, return Http::FilterHeadersStatus::StopIteration; } +Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { + const auto* config = getConfig(); + + if (config->enabled() && config->enableXRateLimitHeaders()) { + ASSERT(stored_descriptors_.has_value()); + auto limit = maxTokens(stored_descriptors_.value()); + auto remaining = remainingTokens(stored_descriptors_.value()); + auto reset = remainingFillInterval(stored_descriptors_.value()); + + headers.addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, limit); + headers.addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, remaining); + headers.addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, reset); + } + + return Http::FilterHeadersStatus::Continue; +} + bool Filter::requestAllowed(absl::Span request_descriptors) { const auto* config = getConfig(); return config->rateLimitPerConnection() @@ -123,6 +166,28 @@ bool Filter::requestAllowed(absl::Span request : config->requestAllowed(request_descriptors); } +uint32_t Filter::maxTokens(absl::Span request_descriptors) { + const auto* config = getConfig(); + return config->rateLimitPerConnection() + ? getPerConnectionRateLimiter().maxTokens(request_descriptors) + : config->maxTokens(request_descriptors); +} + +uint32_t Filter::remainingTokens(absl::Span request_descriptors) { + const auto* config = getConfig(); + return config->rateLimitPerConnection() + ? getPerConnectionRateLimiter().remainingTokens(request_descriptors) + : config->remainingTokens(request_descriptors); +} + +int64_t +Filter::remainingFillInterval(absl::Span request_descriptors) { + const auto* config = getConfig(); + return config->rateLimitPerConnection() + ? getPerConnectionRateLimiter().remainingFillInterval(request_descriptors) + : config->remainingFillInterval(request_descriptors); +} + const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& Filter::getPerConnectionRateLimiter() { const auto* config = getConfig(); ASSERT(config->rateLimitPerConnection()); diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h index 5b485e6b999fb..bbe8238a8db9c 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -70,6 +71,10 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { const LocalInfo::LocalInfo& localInfo() const { return local_info_; } Runtime::Loader& runtime() { return runtime_; } bool requestAllowed(absl::Span request_descriptors) const; + uint32_t maxTokens(absl::Span request_descriptors) const; + uint32_t remainingTokens(absl::Span request_descriptors) const; + int64_t + remainingFillInterval(absl::Span request_descriptors) const; bool enabled() const; bool enforced() const; LocalRateLimitStats& stats() const { return stats_; } @@ -87,6 +92,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { return descriptors_; } bool rateLimitPerConnection() const { return rate_limit_per_connection_; } + bool enableXRateLimitHeaders() const { return enable_x_rate_limit_headers_; } private: friend class FilterTest; @@ -119,6 +125,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { Router::HeaderParserPtr request_headers_parser_; const uint64_t stage_; const bool has_descriptors_; + const bool enable_x_rate_limit_headers_; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -135,6 +142,10 @@ class Filter : public Http::PassThroughFilter { Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) override; + // Http::StreamEncoderFilter + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, + bool end_stream) override; + private: friend class FilterTest; @@ -142,9 +153,14 @@ class Filter : public Http::PassThroughFilter { Http::RequestHeaderMap& headers); const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& getPerConnectionRateLimiter(); bool requestAllowed(absl::Span request_descriptors); + uint32_t maxTokens(absl::Span request_descriptors); + uint32_t remainingTokens(absl::Span request_descriptors); + int64_t remainingFillInterval(absl::Span request_descriptors); const FilterConfig* getConfig() const; FilterConfigSharedPtr config_; + + absl::optional> stored_descriptors_; }; } // namespace LocalRateLimitFilter diff --git a/source/extensions/filters/http/ratelimit/BUILD b/source/extensions/filters/http/ratelimit/BUILD index 4b2890af5b493..fd4c15c81acee 100644 --- a/source/extensions/filters/http/ratelimit/BUILD +++ b/source/extensions/filters/http/ratelimit/BUILD @@ -38,6 +38,7 @@ envoy_cc_library( deps = [ "//source/common/http:header_map_lib", "//source/extensions/filters/common/ratelimit:ratelimit_client_interface", + "//source/extensions/filters/http/common:ratelimit_headers_lib", ], ) diff --git a/source/extensions/filters/http/ratelimit/ratelimit_headers.cc b/source/extensions/filters/http/ratelimit/ratelimit_headers.cc index 76644be416df2..2bffd908f4cba 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit_headers.cc +++ b/source/extensions/filters/http/ratelimit/ratelimit_headers.cc @@ -1,6 +1,7 @@ #include "source/extensions/filters/http/ratelimit/ratelimit_headers.h" #include "source/common/http/header_map_impl.h" +#include "source/extensions/filters/http/common/ratelimit_headers.h" #include "absl/strings/substitute.h" @@ -34,14 +35,15 @@ Http::ResponseHeaderMapPtr XRateLimitHeaderUtils::create( // Example of the result: `, 10;w=1;name="per-ip", 1000;w=3600` if (window) { // For each descriptor status append `;w=` - absl::SubstituteAndAppend("a_policy, ", $0;$1=$2", - status.current_limit().requests_per_unit(), - XRateLimitHeaders::get().QuotaPolicyKeys.Window, window); + absl::SubstituteAndAppend( + "a_policy, ", $0;$1=$2", status.current_limit().requests_per_unit(), + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().QuotaPolicyKeys.Window, window); if (!status.current_limit().name().empty()) { // If the descriptor has a name, append `;name=""` - absl::SubstituteAndAppend("a_policy, ";$0=\"$1\"", - XRateLimitHeaders::get().QuotaPolicyKeys.Name, - status.current_limit().name()); + absl::SubstituteAndAppend( + "a_policy, ";$0=\"$1\"", + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().QuotaPolicyKeys.Name, + status.current_limit().name()); } } } @@ -49,11 +51,14 @@ Http::ResponseHeaderMapPtr XRateLimitHeaderUtils::create( if (min_remaining_limit_status) { const std::string rate_limit_limit = absl::StrCat( min_remaining_limit_status.value().current_limit().requests_per_unit(), quota_policy); - result->addReferenceKey(XRateLimitHeaders::get().XRateLimitLimit, rate_limit_limit); - result->addReferenceKey(XRateLimitHeaders::get().XRateLimitRemaining, - min_remaining_limit_status.value().limit_remaining()); - result->addReferenceKey(XRateLimitHeaders::get().XRateLimitReset, - min_remaining_limit_status.value().duration_until_reset().seconds()); + result->addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, rate_limit_limit); + result->addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, + min_remaining_limit_status.value().limit_remaining()); + result->addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, + min_remaining_limit_status.value().duration_until_reset().seconds()); } descriptor_statuses = nullptr; return result; diff --git a/source/extensions/filters/http/ratelimit/ratelimit_headers.h b/source/extensions/filters/http/ratelimit/ratelimit_headers.h index 6717941426560..14c83391e293a 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit_headers.h +++ b/source/extensions/filters/http/ratelimit/ratelimit_headers.h @@ -1,28 +1,11 @@ #pragma once -#include "envoy/http/header_map.h" - -#include "source/common/singleton/const_singleton.h" #include "source/extensions/filters/common/ratelimit/ratelimit.h" namespace Envoy { namespace Extensions { namespace HttpFilters { namespace RateLimitFilter { - -class XRateLimitHeaderValues { -public: - const Http::LowerCaseString XRateLimitLimit{"x-ratelimit-limit"}; - const Http::LowerCaseString XRateLimitRemaining{"x-ratelimit-remaining"}; - const Http::LowerCaseString XRateLimitReset{"x-ratelimit-reset"}; - - struct { - const std::string Window{"w"}; - const std::string Name{"name"}; - } QuotaPolicyKeys; -}; -using XRateLimitHeaders = ConstSingleton; - class XRateLimitHeaderUtils { public: static Http::ResponseHeaderMapPtr diff --git a/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc b/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc index 3965930cfb336..87b05a3d97aa6 100644 --- a/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc +++ b/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc @@ -172,6 +172,42 @@ TEST_F(LocalRateLimiterImplTest, TokenBucketMaxTokensGreaterThanTokensPerFill) { EXPECT_FALSE(rate_limiter_->requestAllowed(route_descriptors_)); } +// Verify token bucket status of max tokens, remaining tokens and remaining fill interval. +TEST_F(LocalRateLimiterImplTest, TokenBucketStatus) { + initialize(std::chrono::milliseconds(3000), 2, 2); + + // 2 -> 1 tokens + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(3000), nullptr)); + EXPECT_TRUE(rate_limiter_->requestAllowed(route_descriptors_)); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 3); + + // 1 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_TRUE(rate_limiter_->requestAllowed(route_descriptors_)); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 2); + + // 0 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_FALSE(rate_limiter_->requestAllowed(route_descriptors_)); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 1); + + // 0 -> 2 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 3); +} + class LocalRateLimiterDescriptorImplTest : public LocalRateLimiterImplTest { public: void initializeWithDescriptor(const std::chrono::milliseconds fill_interval, @@ -394,6 +430,97 @@ TEST_F(LocalRateLimiterDescriptorImplTest, TokenBucketDifferentDescriptorDiffere EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor_)); } +// Verify token bucket status of max tokens, remaining tokens and remaining fill interval. +TEST_F(LocalRateLimiterDescriptorImplTest, TokenBucketDescriptorStatus) { + TestUtility::loadFromYaml(fmt::format(single_descriptor_config_yaml, 2, 2, "3s"), + *descriptors_.Add()); + initializeWithDescriptor(std::chrono::milliseconds(3000), 2, 2); + + // 2 -> 1 tokens + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(3000), nullptr)); + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 1 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 2); + + // 0 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 1); + + // 0 -> 2 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); +} + +// Verify token bucket status of max tokens, remaining tokens and remaining fill interval with +// multiple descriptors. +TEST_F(LocalRateLimiterDescriptorImplTest, TokenBucketDifferentDescriptorStatus) { + TestUtility::loadFromYaml(multiple_descriptor_config_yaml, *descriptors_.Add()); + TestUtility::loadFromYaml(fmt::format(single_descriptor_config_yaml, 2, 2, "3s"), + *descriptors_.Add()); + initializeWithDescriptor(std::chrono::milliseconds(50), 2, 1); + + // 2 -> 1 tokens for descriptor_ + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 1 -> 0 tokens for descriptor_ and descriptor2_ + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor2_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor2_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor2_), 0); + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 0 -> 0 tokens for descriptor_ and descriptor2_ + EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor2_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor2_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor2_), 0); + EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 0 -> 1 tokens for descriptor2_ + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(50), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(50), nullptr)); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor2_), 0); + + // 0 -> 2 tokens for descriptor_ + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(3000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(50), nullptr)); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); +} + } // Namespace LocalRateLimit } // namespace Common } // namespace Filters diff --git a/test/extensions/filters/http/local_ratelimit/filter_test.cc b/test/extensions/filters/http/local_ratelimit/filter_test.cc index 939cebf025baf..16942d5338732 100644 --- a/test/extensions/filters/http/local_ratelimit/filter_test.cc +++ b/test/extensions/filters/http/local_ratelimit/filter_test.cc @@ -40,6 +40,7 @@ stat_prefix: test key: x-local-ratelimited value: 'true' local_rate_limit_per_downstream_connection: {} +enable_x_ratelimit_headers: {} )"; // '{}' used in the yaml config above are position dependent placeholders used for substitutions. // Different test cases toggle functionality based on these positional placeholder variables @@ -99,17 +100,17 @@ class FilterTest : public testing::Test { }; TEST_F(FilterTest, Runtime) { - setup(fmt::format(config_yaml, "1", "false"), false, false); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\""), false, false); EXPECT_EQ(&runtime_, &(config_->runtime())); } TEST_F(FilterTest, ToErrorCode) { - setup(fmt::format(config_yaml, "1", "false"), false, false); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\""), false, false); EXPECT_EQ(Http::Code::BadRequest, toErrorCode(400)); } TEST_F(FilterTest, Disabled) { - setup(fmt::format(config_yaml, "1", "false"), false, false); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\""), false, false); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); EXPECT_EQ(0U, findCounter("test.http_local_rate_limit.enabled")); @@ -117,7 +118,7 @@ TEST_F(FilterTest, Disabled) { } TEST_F(FilterTest, RequestOk) { - setup(fmt::format(config_yaml, "1", "false")); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\"")); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_2_->decodeHeaders(headers, false)); @@ -128,7 +129,7 @@ TEST_F(FilterTest, RequestOk) { } TEST_F(FilterTest, RequestOkPerConnection) { - setup(fmt::format(config_yaml, "1", "true")); + setup(fmt::format(config_yaml, "1", "true", "\"OFF\"")); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_2_->decodeHeaders(headers, false)); @@ -139,7 +140,7 @@ TEST_F(FilterTest, RequestOkPerConnection) { } TEST_F(FilterTest, RequestRateLimited) { - setup(fmt::format(config_yaml, "1", "false")); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\"")); EXPECT_CALL(decoder_callbacks_2_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)) .WillOnce(Invoke([](Http::Code code, absl::string_view body, @@ -179,7 +180,7 @@ connection rate limiting and even though 'max_token' is set to 1, it allows 2 re allowed (across the process) for the same configuration. */ TEST_F(FilterTest, RequestRateLimitedPerConnection) { - setup(fmt::format(config_yaml, "1", "true")); + setup(fmt::format(config_yaml, "1", "true", "\"OFF\"")); EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)) .WillOnce(Invoke([](Http::Code code, absl::string_view body, @@ -216,7 +217,7 @@ TEST_F(FilterTest, RequestRateLimitedPerConnection) { } TEST_F(FilterTest, RequestRateLimitedButNotEnforced) { - setup(fmt::format(config_yaml, "0", "false"), true, false); + setup(fmt::format(config_yaml, "0", "false", "\"OFF\""), true, false); EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)).Times(0); @@ -231,6 +232,29 @@ TEST_F(FilterTest, RequestRateLimitedButNotEnforced) { EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); } +TEST_F(FilterTest, RequestRateLimitedXRateLimitHeaders) { + setup(fmt::format(config_yaml, "1", "false", "DRAFT_VERSION_03")); + + auto request_headers = Http::TestRequestHeaderMapImpl(); + auto response_headers = Http::TestResponseHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + EXPECT_EQ("1", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("1000", response_headers.get_("x-ratelimit-reset")); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_2_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_2_->encodeHeaders(response_headers, false)); + EXPECT_EQ("1", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("1000", response_headers.get_("x-ratelimit-reset")); + EXPECT_EQ(2U, findCounter("test.http_local_rate_limit.enabled")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enforced")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.ok")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); +} + static const std::string descriptor_config_yaml = R"( stat_prefix: test token_bucket: @@ -253,6 +277,7 @@ stat_prefix: test key: x-test-rate-limit value: 'true' local_rate_limit_per_downstream_connection: true +enable_x_ratelimit_headers: {} descriptors: - entries: - key: hello @@ -297,7 +322,7 @@ class DescriptorFilterTest : public FilterTest { }; TEST_F(DescriptorFilterTest, NoRouteEntry) { - setupPerRoute(fmt::format(descriptor_config_yaml, "1", "1", "0"), true, true, true); + setupPerRoute(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0"), true, true, true); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); @@ -307,7 +332,7 @@ TEST_F(DescriptorFilterTest, NoRouteEntry) { } TEST_F(DescriptorFilterTest, NoCluster) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_, clusterInfo()).WillRepeatedly(testing::Return(nullptr)); @@ -319,7 +344,7 @@ TEST_F(DescriptorFilterTest, NoCluster) { } TEST_F(DescriptorFilterTest, DisabledInRoute) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -334,7 +359,7 @@ TEST_F(DescriptorFilterTest, DisabledInRoute) { } TEST_F(DescriptorFilterTest, RouteDescriptorRequestOk) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -350,7 +375,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorRequestOk) { } TEST_F(DescriptorFilterTest, RouteDescriptorRequestRatelimited) { - setUpTest(fmt::format(descriptor_config_yaml, "0", "0", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "0", "\"OFF\"", "0", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -366,7 +391,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorRequestRatelimited) { } TEST_F(DescriptorFilterTest, RouteDescriptorNotFound) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -383,7 +408,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorNotFound) { TEST_F(DescriptorFilterTest, RouteDescriptorFirstMatch) { // Request should not be rate limited as it should match first descriptor with 10 req/min - setUpTest(fmt::format(descriptor_config_yaml, "0", "0", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "0", "\"OFF\"", "0", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -399,7 +424,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorFirstMatch) { } TEST_F(DescriptorFilterTest, RouteDescriptorWithStageConfig) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "1")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "1")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(1)); @@ -414,6 +439,29 @@ TEST_F(DescriptorFilterTest, RouteDescriptorWithStageConfig) { EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.ok")); } +TEST_F(DescriptorFilterTest, RouteDescriptorRequestRatelimitedXRateLimitHeaders) { + setUpTest(fmt::format(descriptor_config_yaml, "0", "DRAFT_VERSION_03", "0", "0")); + + EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, + getApplicableRateLimit(0)); + + EXPECT_CALL(route_rate_limit_, populateLocalDescriptors(_, _, _, _)) + .WillOnce(testing::SetArgReferee<0>(descriptor_)); + + auto request_headers = Http::TestRequestHeaderMapImpl(); + auto response_headers = Http::TestResponseHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("60", response_headers.get_("x-ratelimit-reset")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enabled")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enforced")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); +} + } // namespace LocalRateLimitFilter } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc index f125f8f722ef2..5ed1299c2ff6c 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc @@ -8,8 +8,8 @@ #include "source/common/buffer/zero_copy_input_stream_impl.h" #include "source/common/grpc/codec.h" #include "source/common/grpc/common.h" +#include "source/extensions/filters/http/common/ratelimit_headers.h" #include "source/extensions/filters/http/ratelimit/config.h" -#include "source/extensions/filters/http/ratelimit/ratelimit_headers.h" #include "test/common/grpc/grpc_client_integration.h" #include "test/extensions/filters/common/ratelimit/utils.h" @@ -406,17 +406,18 @@ TEST_P(RatelimitFilterHeadersEnabledIntegrationTest, OkWithFilterHeaders) { EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitLimit, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, "1, 1;w=60;name=\"first\", 4;w=3600;name=\"second\"")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitRemaining, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, "2")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitReset, "3")); + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, + "3")); cleanup(); @@ -442,17 +443,18 @@ TEST_P(RatelimitFilterHeadersEnabledIntegrationTest, OverLimitWithFilterHeaders) EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitLimit, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, "1, 1;w=60;name=\"first\", 4;w=3600;name=\"second\"")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitRemaining, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, "2")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitReset, "3")); + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, + "3")); cleanup(); diff --git a/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc b/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc index c13c03b5f8760..d2cf7c8290caf 100644 --- a/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc +++ b/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc @@ -50,6 +50,11 @@ DEFINE_PROTO_FUZZER( return; } static NiceMock dispatcher; + // TODO(zhxie): The GlobalTimeSystem in MockDispatcher will initialize itself into a + // TestRealTimeSystem by default which is incompatible with the SimulatedTimeSystem in + // MockReadFilterCallbacks. We will not need to change the time system after the switching of + // default time system in GlobalTimeSystem. + dispatcher.time_system_ = std::make_unique(); Stats::IsolatedStoreImpl stats_store; static NiceMock runtime; Event::MockTimer* fill_timer = new Event::MockTimer(&dispatcher); diff --git a/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc b/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc index 50568d622f69c..222c9a5105a54 100644 --- a/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc +++ b/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc @@ -21,7 +21,7 @@ namespace Extensions { namespace NetworkFilters { namespace LocalRateLimitFilter { -class LocalRateLimitTestBase : public testing::Test { +class LocalRateLimitTestBase : public testing::Test, public Event::TestUsingSimulatedTime { public: void initialize(const std::string& filter_yaml, bool expect_timer_create = true) { envoy::extensions::filters::network::local_ratelimit::v3::LocalRateLimit proto_config;