diff --git a/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto b/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto index 33d415ab4391f..9bb3603f9ebd6 100644 --- a/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto +++ b/api/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Admission Control] // [#extension: envoy.filters.http.admission_control] -// [#next-free-field: 6] +// [#next-free-field: 8] message AdmissionControl { // Default method of specifying what constitutes a successful request. All status codes that // indicate a successful request must be explicitly specified if not relying on the default @@ -91,4 +91,13 @@ message AdmissionControl { // below this threshold, rejection probability will increase. Any success rate above the threshold // results in a rejection probability of 0. Defaults to 95%. config.core.v3.RuntimePercent sr_threshold = 5; + + // If the average RPS of the sampling window is below this threshold, the request + // will not be rejected, even if the success rate is lower than sr_threshold. + // Defaults to 0. + config.core.v3.RuntimeUInt32 rps_threshold = 6; + + // The probability of rejection will never exceed this value, even if the failure rate is rising. + // Defaults to 80%. + config.core.v3.RuntimePercent max_rejection_probability = 7; } diff --git a/docs/root/configuration/http/http_filters/admission_control_filter.rst b/docs/root/configuration/http/http_filters/admission_control_filter.rst index 146b50dc31c39..ac974b2f4067d 100644 --- a/docs/root/configuration/http/http_filters/admission_control_filter.rst +++ b/docs/root/configuration/http/http_filters/admission_control_filter.rst @@ -41,6 +41,11 @@ where, rejection probability will be higher for higher success rates. See `Aggression`_ for a more detailed explanation. +Note that there are additional parameters that affect the rejection probability: + +- *rps_threshold* is a configurable value that when RPS is lower than it, requests will pass through the filter. +- *max_reject_probability* represents the upper limit of the rejection probability. + .. note:: The success rate calculations are performed on a per-thread basis for increased performance. In addition, the per-thread isolation prevents decreases the blast radius of a single bad connection @@ -91,6 +96,12 @@ fields can be overridden via runtime settings. aggression: default_value: 1.5 runtime_key: "admission_control.aggression" + rps_threshold: + default_value: 5 + runtime_key: "admission_control.rps_threshold" + max_rejection_probability: + default_value: 80.0 + runtime_key: "admission_control.max_rejection_probability" success_criteria: http_criteria: http_success_status: @@ -110,6 +121,8 @@ The above configuration can be understood as follows: window. * HTTP requests are considered successful if they are 1xx, 2xx, 3xx, or a 404. * gRPC requests are considered successful if they are OK or CANCELLED. +* Requests will never be rejeted from this filter if the RPS is lower than 5. +* Rejection probability will never exceed 80% even if the failure rate is 100%. Statistics ---------- diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3e88b63f3c90b..5ad5f338cb915 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -15,6 +15,7 @@ Minor Behavior Changes * access_log: add new access_log command operator ``%REQUEST_TX_DURATION%``. * access_log: remove extra quotes on metadata string values. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.unquote_log_string_values`` to false. +* admission control: added :ref:`admission control ` whose default value is 80%, which means that the upper limit of the default rejection probability of the filter is changed from 100% to 80%. * aws_request_signing: requests are now buffered by default to compute signatures which include the payload hash, making the filter compatible with most AWS services. Previously, requests were never buffered, which only produced correct signatures for requests without a body, or for @@ -68,6 +69,7 @@ Removed Config or Runtime New Features ------------ +* admission control: added :ref:`admission control ` option that when average RPS of the sampling window is below this threshold, the filter will not throttle requests. Added :ref:`admission control ` option to set an upper limit on the probability of rejection. * bandwidth_limit: added new :ref:`HTTP bandwidth limit filter `. * bootstrap: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting one such configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains. And by setting the configuration *resolvers* we can specify the external DNS servers to be used for external DNS query. * cluster: added :ref:`dns_resolution_config ` to aggregate all of the DNS resolver configuration in a single message. By setting one such configuration option *no_default_search_domain* as true the DNS resolver will not use the default search domains. diff --git a/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto b/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto index 33d415ab4391f..9bb3603f9ebd6 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/admission_control/v3alpha/admission_control.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Admission Control] // [#extension: envoy.filters.http.admission_control] -// [#next-free-field: 6] +// [#next-free-field: 8] message AdmissionControl { // Default method of specifying what constitutes a successful request. All status codes that // indicate a successful request must be explicitly specified if not relying on the default @@ -91,4 +91,13 @@ message AdmissionControl { // below this threshold, rejection probability will increase. Any success rate above the threshold // results in a rejection probability of 0. Defaults to 95%. config.core.v3.RuntimePercent sr_threshold = 5; + + // If the average RPS of the sampling window is below this threshold, the request + // will not be rejected, even if the success rate is lower than sr_threshold. + // Defaults to 0. + config.core.v3.RuntimeUInt32 rps_threshold = 6; + + // The probability of rejection will never exceed this value, even if the failure rate is rising. + // Defaults to 80%. + config.core.v3.RuntimePercent max_rejection_probability = 7; } diff --git a/source/common/runtime/runtime_protos.h b/source/common/runtime/runtime_protos.h index e8b6f2381911b..a17f61519d67c 100644 --- a/source/common/runtime/runtime_protos.h +++ b/source/common/runtime/runtime_protos.h @@ -11,7 +11,6 @@ namespace Envoy { namespace Runtime { -// TODO(WeavingGao): use for #16392 // Helper class for runtime-derived uint32. class UInt32 : Logger::Loggable { public: diff --git a/source/extensions/filters/http/admission_control/admission_control.cc b/source/extensions/filters/http/admission_control/admission_control.cc index 3576dfb9099d5..bc755ae01e8a7 100644 --- a/source/extensions/filters/http/admission_control/admission_control.cc +++ b/source/extensions/filters/http/admission_control/admission_control.cc @@ -30,6 +30,8 @@ using GrpcStatus = Grpc::Status::GrpcStatus; static constexpr double defaultAggression = 1.0; static constexpr double defaultSuccessRateThreshold = 95.0; +static constexpr uint32_t defaultRpsThreshold = 0; +static constexpr double defaultMaxRejectionProbability = 80.0; AdmissionControlFilterConfig::AdmissionControlFilterConfig( const AdmissionControlProto& proto_config, Runtime::Loader& runtime, @@ -44,6 +46,13 @@ AdmissionControlFilterConfig::AdmissionControlFilterConfig( sr_threshold_(proto_config.has_sr_threshold() ? std::make_unique( proto_config.sr_threshold(), runtime) : nullptr), + rps_threshold_(proto_config.has_rps_threshold() + ? std::make_unique(proto_config.rps_threshold(), runtime) + : nullptr), + max_rejection_probability_(proto_config.has_max_rejection_probability() + ? std::make_unique( + proto_config.max_rejection_probability(), runtime) + : nullptr), response_evaluator_(std::move(response_evaluator)) {} double AdmissionControlFilterConfig::aggression() const { @@ -55,6 +64,16 @@ double AdmissionControlFilterConfig::successRateThreshold() const { return std::min(pct, 100.0) / 100.0; } +uint32_t AdmissionControlFilterConfig::rpsThreshold() const { + return rps_threshold_ ? rps_threshold_->value() : defaultRpsThreshold; +} + +double AdmissionControlFilterConfig::maxRejectionProbability() const { + const double ret = max_rejection_probability_ ? max_rejection_probability_->value() + : defaultMaxRejectionProbability; + return ret / 100.0; +} + AdmissionControlFilter::AdmissionControlFilter(AdmissionControlFilterConfigSharedPtr config, const std::string& stats_prefix) : config_(std::move(config)), stats_(generateStats(config_->scope(), stats_prefix)), @@ -67,6 +86,11 @@ Http::FilterHeadersStatus AdmissionControlFilter::decodeHeaders(Http::RequestHea return Http::FilterHeadersStatus::Continue; } + if (config_->getController().averageRps() < config_->rpsThreshold()) { + ENVOY_LOG(debug, "Current rps: {} is below rps_threshold: {}, continue"); + return Http::FilterHeadersStatus::Continue; + } + if (shouldRejectRequest()) { // We do not want to sample requests that we are rejecting, since this taints the measurements // that should be describing the upstreams. In addition, if we were to record the requests @@ -147,6 +171,7 @@ bool AdmissionControlFilter::shouldRejectRequest() const { if (aggression != 1.0) { probability = std::pow(probability, 1.0 / aggression); } + probability = std::min(probability, config_->maxRejectionProbability()); // Choosing an accuracy of 4 significant figures for the probability. static constexpr uint64_t accuracy = 1e4; diff --git a/source/extensions/filters/http/admission_control/admission_control.h b/source/extensions/filters/http/admission_control/admission_control.h index 7e6b0d159b876..4d921eddbc108 100644 --- a/source/extensions/filters/http/admission_control/admission_control.h +++ b/source/extensions/filters/http/admission_control/admission_control.h @@ -65,6 +65,8 @@ class AdmissionControlFilterConfig { Stats::Scope& scope() const { return scope_; } double aggression() const; double successRateThreshold() const; + uint32_t rpsThreshold() const; + double maxRejectionProbability() const; ResponseEvaluator& responseEvaluator() const { return *response_evaluator_; } private: @@ -74,6 +76,8 @@ class AdmissionControlFilterConfig { Runtime::FeatureFlag admission_control_feature_; std::unique_ptr aggression_; std::unique_ptr sr_threshold_; + std::unique_ptr rps_threshold_; + std::unique_ptr max_rejection_probability_; std::shared_ptr response_evaluator_; }; diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.cc b/source/extensions/filters/http/admission_control/thread_local_controller.cc index e98ee62433cbb..61ea10dc3f9df 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.cc +++ b/source/extensions/filters/http/admission_control/thread_local_controller.cc @@ -17,6 +17,15 @@ ThreadLocalControllerImpl::ThreadLocalControllerImpl(TimeSource& time_source, std::chrono::seconds sampling_window) : time_source_(time_source), sampling_window_(sampling_window) {} +uint32_t ThreadLocalControllerImpl::averageRps() const { + if (historical_data_.empty() || global_data_.requests == 0) { + return 0; + } + using std::chrono::seconds; + seconds secs = std::max(seconds(1), std::chrono::duration_cast(ageOfOldestSample())); + return global_data_.requests / secs.count(); +} + void ThreadLocalControllerImpl::maybeUpdateHistoricalData() { // Purge stale samples. while (!historical_data_.empty() && ageOfOldestSample() >= sampling_window_) { diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.h b/source/extensions/filters/http/admission_control/thread_local_controller.h index fde56131ecd87..4c536ba1dd55c 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.h +++ b/source/extensions/filters/http/admission_control/thread_local_controller.h @@ -37,6 +37,9 @@ class ThreadLocalController { // Returns the current number of requests and how many of them are successful. virtual RequestData requestCounts() PURE; + + // return the average RPS across the sampling window + virtual uint32_t averageRps() const PURE; }; /** @@ -63,6 +66,8 @@ class ThreadLocalControllerImpl : public ThreadLocalController, return global_data_; } + uint32_t averageRps() const override; + private: void recordRequest(bool success); diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index a26dedf252da8..eaea6c0c5cbd4 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -36,6 +36,7 @@ class MockThreadLocalController : public ThreadLocal::ThreadLocalObject, MOCK_METHOD(RequestData, requestCounts, ()); MOCK_METHOD(void, recordSuccess, ()); MOCK_METHOD(void, recordFailure, ()); + MOCK_METHOD(uint32_t, averageRps, (), (const)); }; class MockResponseEvaluator : public ResponseEvaluator { @@ -141,6 +142,10 @@ sampling_window: 10s aggression: default_value: 1.0 runtime_key: "foo.aggression" +max_rejection_probability: + default_value: + value: 100.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -170,6 +175,7 @@ sampling_window: 10s // The filter is bypassed via runtime. EXPECT_CALL(controller_, requestCounts()).Times(0); + EXPECT_CALL(controller_, averageRps()).Times(0); // We expect no rejections. Http::TestRequestHeaderMapImpl request_headers; @@ -188,6 +194,7 @@ TEST_F(AdmissionControlTest, DisregardHealthChecks) { // We do not make admission decisions for health checks, so we expect no lookup of request success // counts. EXPECT_CALL(controller_, requestCounts()).Times(0); + EXPECT_CALL(controller_, averageRps()).Times(0); Http::TestRequestHeaderMapImpl request_headers; Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; @@ -205,6 +212,7 @@ TEST_F(AdmissionControlTest, HttpFailureBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -224,6 +232,7 @@ TEST_F(AdmissionControlTest, HttpSuccessBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isHttpSuccess(200)).WillRepeatedly(Return(true)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -241,6 +250,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -260,6 +270,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehaviorTrailer) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -278,6 +289,7 @@ TEST_F(AdmissionControlTest, GrpcFailureBehaviorTrailer) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); EXPECT_CALL(*evaluator_, isGrpcSuccess(7)).WillRepeatedly(Return(false)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, @@ -297,6 +309,7 @@ TEST_F(AdmissionControlTest, GrpcSuccessBehavior) { EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 100))); EXPECT_CALL(*evaluator_, isGrpcSuccess(0)).WillRepeatedly(Return(true)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(99)); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); @@ -320,6 +333,10 @@ sampling_window: 10s aggression: default_value: 1.0 runtime_key: "foo.aggression" +max_rejection_probability: + default_value: + value: 80.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -335,6 +352,8 @@ sampling_window: 10s // Increase aggression and expect higher rejection probabilities for the same values. EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(2.0)); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(100.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(80.0)); verifyProbabilities(100, 0.0); verifyProbabilities(95, 0.22); verifyProbabilities(75, 0.5); @@ -343,12 +362,113 @@ sampling_window: 10s // from there. EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(1.0)); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(95.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(80.0)); verifyProbabilities(100, 0.0); verifyProbabilities(98, 0.0); verifyProbabilities(95, 0.0); verifyProbabilities(90, 0.05); verifyProbabilities(75, 0.20); verifyProbabilities(50, 0.46); + + // Validate max rejection probability + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(1.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(100.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(10.0)); + + verifyProbabilities(100, 0.0); + verifyProbabilities(95, 0.05); + verifyProbabilities(80, 0.1); + verifyProbabilities(0, 0.1); +} + +// Validate RPS threshold. +TEST_F(AdmissionControlTest, RpsThreshold) { + std::string yaml = R"EOF( +enabled: + default_value: true + runtime_key: "foo.enabled" +sampling_window: 10s +aggression: + default_value: 1.0 + runtime_key: "foo.aggression" +rps_threshold: + default_value: 0 + runtime_key: "foo.rps_threshold" +max_rejection_probability: + default_value: + value: 100.0 + runtime_key: "foo.max_rejection_probability" +success_criteria: + http_criteria: + grpc_criteria: +)EOF"; + + auto config = makeConfig(yaml); + setupFilter(config); + + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 0)).WillRepeatedly(Return(10)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(1)); + EXPECT_CALL(controller_, requestCounts()).Times(0); + + // Continue expected. + Http::TestRequestHeaderMapImpl request_headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 0)).WillRepeatedly(Return(10)); + EXPECT_CALL(controller_, averageRps()).WillRepeatedly(Return(100)); + + // We expect rejection counter to increment upon failure. + TestUtility::waitForCounterEq(scope_, "test_prefix.rq_rejected", 0, time_system_); + + EXPECT_CALL(controller_, requestCounts()).WillRepeatedly(Return(RequestData(100, 0))); + EXPECT_CALL(*evaluator_, isHttpSuccess(500)).WillRepeatedly(Return(false)); + + // StopIteration expected. + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, true)); + sampleHttpRequest("500"); + + TestUtility::waitForCounterEq(scope_, "test_prefix.rq_rejected", 1, time_system_); +} + +// Validate max rejection probability. +TEST_F(AdmissionControlTest, MaxRejectionProbability) { + std::string yaml = R"EOF( +enabled: + default_value: true + runtime_key: "foo.enabled" +sampling_window: 10s +sr_threshold: + default_value: + value: 100.0 + runtime_key: "foo.threshold" +aggression: + default_value: 1.0 + runtime_key: "foo.aggression" +max_rejection_probability: + default_value: + value: 80.0 + runtime_key: "foo.max_rejection_probability" +success_criteria: + http_criteria: + grpc_criteria: +)EOF"; + + auto config = makeConfig(yaml); + setupFilter(config); + + // Validate max rejection probability + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.aggression", 1.0)).WillRepeatedly(Return(1.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.threshold", 100.0)).WillRepeatedly(Return(100.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 80.0)) + .WillRepeatedly(Return(10.0)); + + verifyProbabilities(100, 0.0); + verifyProbabilities(95, 0.05); + verifyProbabilities(80, 0.1); + verifyProbabilities(0, 0.1); } } // namespace diff --git a/test/extensions/filters/http/admission_control/admission_control_integration_test.cc b/test/extensions/filters/http/admission_control/admission_control_integration_test.cc index 5ac69fef7e1fe..11357d799626e 100644 --- a/test/extensions/filters/http/admission_control/admission_control_integration_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_integration_test.cc @@ -117,9 +117,9 @@ TEST_P(AdmissionControlIntegrationTest, HttpTest) { ++request_count; } - // Given the current throttling rate formula with an aggression of 1, it should result in a ~98% - // throttling rate. Allowing an error of 5%. - EXPECT_NEAR(throttle_count / request_count, 0.98, 0.05); + // Given the current throttling rate formula with an aggression of 1, it should result in a ~80% + // throttling rate (default max_rejection_probability). Allowing an error of 5%. + EXPECT_NEAR(throttle_count / request_count, 0.80, 0.05); // We now wait for the history to become stale. timeSystem().advanceTimeWait(std::chrono::seconds(120)); @@ -157,9 +157,9 @@ TEST_P(AdmissionControlIntegrationTest, GrpcTest) { ++request_count; } - // Given the current throttling rate formula with an aggression of 1, it should result in a ~98% - // throttling rate. Allowing an error of 5%. - EXPECT_NEAR(throttle_count / request_count, 0.98, 0.05); + // Given the current throttling rate formula with an aggression of 1, it should result in a ~80% + // throttling rate (default max_rejection_probability). Allowing an error of 5%. + EXPECT_NEAR(throttle_count / request_count, 0.80, 0.05); // We now wait for the history to become stale. timeSystem().advanceTimeWait(std::chrono::seconds(120)); diff --git a/test/extensions/filters/http/admission_control/config_test.cc b/test/extensions/filters/http/admission_control/config_test.cc index 2ce74fa135f1c..80e1d9ce81ae5 100644 --- a/test/extensions/filters/http/admission_control/config_test.cc +++ b/test/extensions/filters/http/admission_control/config_test.cc @@ -118,6 +118,13 @@ sampling_window: 1337s aggression: default_value: 4.2 runtime_key: "foo.aggression" +rps_threshold: + default_value: 5 + runtime_key: "foo.rps_threshold" +max_rejection_probability: + default_value: + value: 70.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -128,6 +135,8 @@ sampling_window: 1337s EXPECT_FALSE(config->filterEnabled()); EXPECT_EQ(4.2, config->aggression()); EXPECT_EQ(0.92, config->successRateThreshold()); + EXPECT_EQ(5, config->rpsThreshold()); + EXPECT_EQ(0.7, config->maxRejectionProbability()); } // Verify the config defaults when not specified. @@ -145,6 +154,8 @@ TEST_F(AdmissionControlConfigTest, BasicTestMinimumConfigured) { EXPECT_TRUE(config->filterEnabled()); EXPECT_EQ(1.0, config->aggression()); EXPECT_EQ(0.95, config->successRateThreshold()); + EXPECT_EQ(0, config->rpsThreshold()); + EXPECT_EQ(0.8, config->maxRejectionProbability()); } // Ensure runtime fields are honored. @@ -161,6 +172,13 @@ sampling_window: 1337s aggression: default_value: 4.2 runtime_key: "foo.aggression" +rps_threshold: + default_value: 5 + runtime_key: "foo.rps_threshold" +max_rejection_probability: + default_value: + value: 70.0 + runtime_key: "foo.max_rejection_probability" success_criteria: http_criteria: grpc_criteria: @@ -174,12 +192,25 @@ sampling_window: 1337s EXPECT_EQ(1.3, config->aggression()); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.sr_threshold", 92)).WillOnce(Return(24.0)); EXPECT_EQ(0.24, config->successRateThreshold()); + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 5)).WillOnce(Return(12)); + EXPECT_EQ(12, config->rpsThreshold()); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 70.0)) + .WillOnce(Return(32.0)); + EXPECT_EQ(0.32, config->maxRejectionProbability()); // Verify bogus runtime thresholds revert to the default value. EXPECT_CALL(runtime_.snapshot_, getDouble("foo.sr_threshold", 92)).WillOnce(Return(250.0)); EXPECT_EQ(0.92, config->successRateThreshold()); EXPECT_CALL(runtime_.snapshot_, getDouble("foo.sr_threshold", 92)).WillOnce(Return(-1.0)); EXPECT_EQ(0.92, config->successRateThreshold()); + EXPECT_CALL(runtime_.snapshot_, getInteger("foo.rps_threshold", 5)).WillOnce(Return(99ull << 40)); + EXPECT_EQ(5, config->rpsThreshold()); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 70.0)) + .WillOnce(Return(-2.0)); + EXPECT_EQ(0.7, config->maxRejectionProbability()); + EXPECT_CALL(runtime_.snapshot_, getDouble("foo.max_rejection_probability", 70.0)) + .WillOnce(Return(300.0)); + EXPECT_EQ(0.7, config->maxRejectionProbability()); } } // namespace diff --git a/test/extensions/filters/http/admission_control/controller_test.cc b/test/extensions/filters/http/admission_control/controller_test.cc index 8c9593501fe18..2b74282519031 100644 --- a/test/extensions/filters/http/admission_control/controller_test.cc +++ b/test/extensions/filters/http/admission_control/controller_test.cc @@ -100,6 +100,37 @@ TEST_F(ThreadLocalControllerTest, VerifyMemoryUsage) { EXPECT_EQ(RequestData(3, 3), tlc_.requestCounts()); } +// Test for function: averageRps. +TEST_F(ThreadLocalControllerTest, AverageRps) { + // Validate that historical_data_ is empty. + EXPECT_EQ(0, tlc_.averageRps()); + + // Validate that global_data_.requests equals to zero. + tlc_.requestCounts(); + EXPECT_EQ(0, tlc_.averageRps()); + + // Validate the value in one second. + tlc_.recordSuccess(); + tlc_.recordFailure(); + EXPECT_EQ(2, tlc_.averageRps()); + + // Validate that the sampling window is not full. + time_system_.advanceTimeWait(std::chrono::seconds(1)); + tlc_.recordSuccess(); + EXPECT_EQ(3, tlc_.averageRps()); + + // Now clean up the sampling window to validate that the sampling window is full. + time_system_.advanceTimeWait(std::chrono::seconds(10)); + for (int tick = 0; tick < window_.count(); ++tick) { + // 1 + 3 + 5 + 7 + 9 + for (int record_count = 0; record_count <= tick * 2; ++record_count) { + tlc_.recordSuccess(); + } + time_system_.advanceTimeWait(std::chrono::seconds(1)); + } + EXPECT_EQ(5, tlc_.averageRps()); +} + } // namespace } // namespace AdmissionControl } // namespace HttpFilters