Repository navigation
fault: add dry_run mode for HTTP fault abort injection #48944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mw-tianmi
wants to merge
4
commits into
envoyproxy:main
Choose a base branch
from
mw-tianmi:fault-abort-dry-run
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
ab090d7
fault: add dry_run mode for HTTP fault abort injection
mw-tianmi 124d6cc
fault: address PR review comments for abort dry_run
mw-tianmi 5a48010
Merge remote-tracking branch 'upstream/main' into fault-abort-dry-run
mw-tianmi 85fe7d6
fault: update aborts_dry_run for explicit stats tags to resolve merge…
mw-tianmi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,6 +109,8 @@ areas: | |
| ext_proc: | ||
| title: ext_proc | ||
|
|
||
| fault: | ||
| title: fault | ||
| filter_chain: | ||
| title: filter_chain | ||
| formatter: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Added :ref:`dry_run <envoy_v3_api_field_extensions.filters.http.fault.v3.FaultAbort.dry_run>` to the | ||
| HTTP fault injection filter to support dry-run abort evaluation. When enabled, requests matching an | ||
| abort fault increment the ``aborts_dry_run`` :ref:`counter <config_http_filters_fault_injection_stats>` | ||
| and record configured :ref:`filter_metadata | ||
| <envoy_v3_api_field_extensions.filters.http.fault.v3.HTTPFault.filter_metadata>` in dynamic metadata | ||
| without aborting the request, while any configured delay or response rate limit is still injected. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -86,11 +86,13 @@ FaultFilterConfig::FaultFilterConfig( | |
| : settings_(fault, context), runtime_(context.runtime()), | ||
| stats_(generateStats(stats_prefix, scope)), scope_(scope), time_source_(context.timeSource()), | ||
| stat_name_set_(scope.symbolTable().makeSet("Fault")), | ||
| aborts_dry_run_(stat_name_set_->add("aborts_dry_run")), | ||
| aborts_injected_(stat_name_set_->add("aborts_injected")), | ||
| delays_injected_(stat_name_set_->add("delays_injected")), | ||
| stats_prefix_(stat_name_set_->add(absl::StrCat(stats_prefix, "fault"))), | ||
| downstream_cluster_tag_( | ||
| stat_name_set_->add(Envoy::Config::TagNames::get().FAULT_DOWNSTREAM_CLUSTER)), | ||
| aborts_dry_run_base_(stat_name_set_->add(absl::StrCat(stats_prefix, "fault.aborts_dry_run"))), | ||
| aborts_injected_base_( | ||
| stat_name_set_->add(absl::StrCat(stats_prefix, "fault.aborts_injected"))), | ||
| delays_injected_base_( | ||
|
|
@@ -183,9 +185,7 @@ bool FaultFilter::maybeSetupDelay(const Http::RequestHeaderMap& request_headers) | |
| delay_timer_->enableTimer(duration.value(), &decoder_callbacks_->scope()); | ||
| recordDelaysInjectedStats(); | ||
| decoder_callbacks_->streamInfo().setResponseFlag(StreamInfo::CoreResponseFlag::DelayInjected); | ||
| auto& dynamic_metadata = decoder_callbacks_->streamInfo().dynamicMetadata(); | ||
| (*dynamic_metadata.mutable_filter_metadata())[decoder_callbacks_->filterConfigName()] = | ||
| fault_settings_->filterMetadata(); | ||
| setFilterMetadata(); | ||
| return true; | ||
| } | ||
| return false; | ||
|
|
@@ -196,9 +196,15 @@ bool FaultFilter::maybeDoAbort(const Http::RequestHeaderMap& request_headers) { | |
| std::optional<Grpc::Status::GrpcStatus> grpc_status; | ||
| std::tie(http_status, grpc_status) = abortStatus(request_headers); | ||
|
|
||
| if (http_status.has_value() && tryIncActiveFaults()) { | ||
| abortWithStatus(http_status.value(), grpc_status); | ||
| return true; | ||
| if (http_status.has_value()) { | ||
| if (isAbortDryRun()) { | ||
| onAbortDryRun(); | ||
| return false; | ||
| } | ||
| if (tryIncActiveFaults()) { | ||
| abortWithStatus(http_status.value(), grpc_status); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
|
|
@@ -265,6 +271,10 @@ bool FaultFilter::isDelayEnabled(const Http::RequestHeaderMap& request_headers) | |
| request_delay->percentage(&request_headers)); | ||
| } | ||
|
|
||
| bool FaultFilter::isAbortDryRun() const { | ||
| return fault_settings_->requestAbort() != nullptr && fault_settings_->requestAbort()->dryRun(); | ||
| } | ||
|
|
||
| bool FaultFilter::isAbortEnabled(const Http::RequestHeaderMap& request_headers) { | ||
| const auto request_abort = fault_settings_->requestAbort(); | ||
| if (request_abort == nullptr) { | ||
|
|
@@ -390,6 +400,15 @@ void FaultFilter::recordDelaysInjectedStats() { | |
| config_->stats().delays_injected_.inc(); | ||
| } | ||
|
|
||
| void FaultFilter::recordAbortsDryRunStats() { | ||
| // Downstream specific stats. | ||
| if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { | ||
| config_->incAbortsDryRun(downstream_cluster_storage_->statName()); | ||
| } | ||
|
|
||
| config_->stats().aborts_dry_run_.inc(); | ||
| } | ||
|
|
||
| void FaultFilter::recordAbortsInjectedStats() { | ||
| // Downstream specific stats. | ||
| if (!downstream_cluster_.empty() && !fault_settings_->disableDownstreamClusterStats()) { | ||
|
|
@@ -464,30 +483,32 @@ void FaultFilter::postDelayInjection(const Http::RequestHeaderMap& request_heade | |
| std::tie(http_status, grpc_status) = abortStatus(request_headers); | ||
|
|
||
| if (http_status.has_value()) { | ||
| abortWithStatus(http_status.value(), grpc_status); | ||
| } else { | ||
| // Should not continue to count as an active fault after the delay has elapsed if no other type | ||
| // of fault is active. As the delay timer is always done at this point and followed abort faults | ||
| // have been checked earlier, here we just check if there's a response rate limit configured. | ||
| ASSERT(fault_active_); | ||
| ASSERT(delay_timer_ == nullptr); | ||
| if (!isResponseRateLimitConfigured()) { | ||
| config_->stats().active_faults_.dec(); | ||
| fault_active_ = false; | ||
| if (!isAbortDryRun()) { | ||
| abortWithStatus(http_status.value(), grpc_status); | ||
| return; | ||
| } | ||
| onAbortDryRun(); | ||
| } | ||
|
|
||
| // Continue request processing. | ||
| decoder_callbacks_->continueDecoding(); | ||
| // Should not continue to count as an active fault after the delay has elapsed if no other type | ||
| // of fault is active. As the delay timer is always done at this point and followed abort faults | ||
| // have been checked earlier, here we just check if there's a response rate limit configured. | ||
| ASSERT(fault_active_); | ||
| ASSERT(delay_timer_ == nullptr); | ||
| if (!isResponseRateLimitConfigured()) { | ||
| config_->stats().active_faults_.dec(); | ||
| fault_active_ = false; | ||
| } | ||
|
|
||
| // Continue request processing. | ||
| decoder_callbacks_->continueDecoding(); | ||
| } | ||
|
|
||
| void FaultFilter::abortWithStatus(Http::Code http_status_code, | ||
| std::optional<Grpc::Status::GrpcStatus> grpc_status) { | ||
| recordAbortsInjectedStats(); | ||
| decoder_callbacks_->streamInfo().setResponseFlag(StreamInfo::CoreResponseFlag::FaultInjected); | ||
| auto& dynamic_metadata = decoder_callbacks_->streamInfo().dynamicMetadata(); | ||
| (*dynamic_metadata.mutable_filter_metadata())[decoder_callbacks_->filterConfigName()] = | ||
| fault_settings_->filterMetadata(); | ||
| setFilterMetadata(); | ||
| absl::string_view body = "fault filter abort"; | ||
| if (fault_settings_->requestAbort() != nullptr && | ||
| !fault_settings_->requestAbort()->responseBody().empty()) { | ||
|
|
@@ -497,6 +518,17 @@ void FaultFilter::abortWithStatus(Http::Code http_status_code, | |
| RcDetails::get().FaultAbort); | ||
| } | ||
|
|
||
| void FaultFilter::onAbortDryRun() { | ||
| recordAbortsDryRunStats(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Following on from the proto comment: setFilterMetadata() replaces the whole namespace struct. With delay + dry-run abort, the delay has already written the real filter_metadata, so a dry-run marker has to merge into it rather than overwrite it. |
||
| setFilterMetadata(); | ||
| } | ||
|
|
||
| void FaultFilter::setFilterMetadata() { | ||
| auto& dynamic_metadata = decoder_callbacks_->streamInfo().dynamicMetadata(); | ||
|
mw-tianmi marked this conversation as resolved.
Resolved
|
||
| (*dynamic_metadata.mutable_filter_metadata())[decoder_callbacks_->filterConfigName()] = | ||
| fault_settings_->filterMetadata(); | ||
| } | ||
|
|
||
| bool FaultFilter::matchesTargetUpstreamCluster() { | ||
| bool matches = true; | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n dry-run mode we write the exact same filter_metadata that a real abort or delay writes, and we deliberately don't set the FaultInjected response flag. So that metadata is the only per-request signal of a dry-run match, and it can't be told apart from a real fault. Anything that already keys off %DYNAMIC_METADATA(envoy.filters.http.fault)% (access logs, dashboards, later filters) will count dry-run traffic as injected faults. That's the opposite of what a dry run should do.
Can we give dry-run its own signal instead of reusing filter_metadata? Concretely:
On a dry-run match, don't write the user's filter_metadata.
Instead, merge a reserved key into the filter's namespace, e.g.
envoy.filters.http.fault: { dry_run_abort: { http_status: 429 } } # or grpc_status for gRPC
Recording the would-be status code is basically free, and it's what operators need when checking a rollout.
Document the key here, and in the filter_metadata field doc ("not written for dry-run matches"), so it's part of the API contract.
If you'd rather let users attach their own payload, an alternative is a separate dry_run_filter_metadata field on HTTPFault. But that adds API surface, and the reserved key seems sufficient to me.
Please also update the changelog entry, which currently says dry-run records filter_metadata. And while you're in this comment, please document two semantics: