diff options
Diffstat (limited to 'grpc/src/core/ext/xds/xds_http_fault_filter.cc')
-rw-r--r-- | grpc/src/core/ext/xds/xds_http_fault_filter.cc | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/grpc/src/core/ext/xds/xds_http_fault_filter.cc b/grpc/src/core/ext/xds/xds_http_fault_filter.cc new file mode 100644 index 00000000..64b7a2b4 --- /dev/null +++ b/grpc/src/core/ext/xds/xds_http_fault_filter.cc @@ -0,0 +1,226 @@ +// +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <grpc/support/port_platform.h> + +#include "src/core/ext/xds/xds_http_fault_filter.h" + +#include <grpc/grpc.h> + +#include <string> + +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "envoy/extensions/filters/common/fault/v3/fault.upb.h" +#include "envoy/extensions/filters/http/fault/v3/fault.upb.h" +#include "envoy/extensions/filters/http/fault/v3/fault.upbdefs.h" +#include "envoy/type/v3/percent.upb.h" +#include "google/protobuf/any.upb.h" +#include "google/protobuf/duration.upb.h" +#include "google/protobuf/wrappers.upb.h" +#include "src/core/ext/filters/fault_injection/fault_injection_filter.h" +#include "src/core/ext/xds/xds_http_filters.h" +#include "src/core/lib/channel/channel_args.h" +#include "src/core/lib/channel/channel_stack.h" +#include "src/core/lib/channel/status_util.h" +#include "src/core/lib/json/json.h" +#include "src/core/lib/transport/status_conversion.h" +#include "upb/def.h" + +namespace grpc_core { + +const char* kXdsHttpFaultFilterConfigName = + "envoy.extensions.filters.http.fault.v3.HTTPFault"; + +namespace { + +uint32_t GetDenominator(const envoy_type_v3_FractionalPercent* fraction) { + if (fraction != nullptr) { + const auto denominator = + static_cast<envoy_type_v3_FractionalPercent_DenominatorType>( + envoy_type_v3_FractionalPercent_denominator(fraction)); + switch (denominator) { + case envoy_type_v3_FractionalPercent_MILLION: + return 1000000; + case envoy_type_v3_FractionalPercent_TEN_THOUSAND: + return 10000; + case envoy_type_v3_FractionalPercent_HUNDRED: + default: + return 100; + } + } + // Use 100 as the default denominator + return 100; +} + +absl::StatusOr<Json> ParseHttpFaultIntoJson(upb_strview serialized_http_fault, + upb_arena* arena) { + auto* http_fault = envoy_extensions_filters_http_fault_v3_HTTPFault_parse( + serialized_http_fault.data, serialized_http_fault.size, arena); + if (http_fault == nullptr) { + return absl::InvalidArgumentError( + "could not parse fault injection filter config"); + } + // NOTE(lidiz): Here, we are manually translating the upb messages into the + // JSON form of the filter config as part of method config, which will be + // directly used later by service config. In this way, we can validate the + // filter configs, and NACK if needed. It also allows the service config to + // function independently without xDS, but not the other way around. + // NOTE(lidiz): please refer to FaultInjectionPolicy for ground truth + // definitions, located at: + // src/core/ext/filters/fault_injection/service_config_parser.h + Json::Object fault_injection_policy_json; + // Section 1: Parse the abort injection config + const auto* fault_abort = + envoy_extensions_filters_http_fault_v3_HTTPFault_abort(http_fault); + if (fault_abort != nullptr) { + grpc_status_code abort_grpc_status_code = GRPC_STATUS_OK; + // Try if gRPC status code is set first + int abort_grpc_status_code_raw = + envoy_extensions_filters_http_fault_v3_FaultAbort_grpc_status( + fault_abort); + if (abort_grpc_status_code_raw != 0) { + if (!grpc_status_code_from_int(abort_grpc_status_code_raw, + &abort_grpc_status_code)) { + return absl::InvalidArgumentError(absl::StrCat( + "invalid gRPC status code: ", abort_grpc_status_code_raw)); + } + } else { + // if gRPC status code is empty, check http status + int abort_http_status_code = + envoy_extensions_filters_http_fault_v3_FaultAbort_http_status( + fault_abort); + if (abort_http_status_code != 0 and abort_http_status_code != 200) { + abort_grpc_status_code = + grpc_http2_status_to_grpc_status(abort_http_status_code); + } + } + // Set the abort_code, even if it's OK + fault_injection_policy_json["abortCode"] = + grpc_status_code_to_string(abort_grpc_status_code); + // Set the headers if we enabled header abort injection control + if (envoy_extensions_filters_http_fault_v3_FaultAbort_has_header_abort( + fault_abort)) { + fault_injection_policy_json["abortCodeHeader"] = + "x-envoy-fault-abort-grpc-request"; + fault_injection_policy_json["abortPercentageHeader"] = + "x-envoy-fault-abort-percentage"; + } + // Set the fraction percent + auto* percent = + envoy_extensions_filters_http_fault_v3_FaultAbort_percentage( + fault_abort); + fault_injection_policy_json["abortPercentageNumerator"] = + Json(envoy_type_v3_FractionalPercent_numerator(percent)); + fault_injection_policy_json["abortPercentageDenominator"] = + Json(GetDenominator(percent)); + } + // Section 2: Parse the delay injection config + const auto* fault_delay = + envoy_extensions_filters_http_fault_v3_HTTPFault_delay(http_fault); + if (fault_delay != nullptr) { + // Parse the delay duration + const auto* delay_duration = + envoy_extensions_filters_common_fault_v3_FaultDelay_fixed_delay( + fault_delay); + if (delay_duration != nullptr) { + fault_injection_policy_json["delay"] = absl::StrFormat( + "%d.%09ds", google_protobuf_Duration_seconds(delay_duration), + google_protobuf_Duration_nanos(delay_duration)); + } + // Set the headers if we enabled header delay injection control + if (envoy_extensions_filters_common_fault_v3_FaultDelay_has_header_delay( + fault_delay)) { + fault_injection_policy_json["delayHeader"] = + "x-envoy-fault-delay-request"; + fault_injection_policy_json["delayPercentageHeader"] = + "x-envoy-fault-delay-request-percentage"; + } + // Set the fraction percent + auto* percent = + envoy_extensions_filters_common_fault_v3_FaultDelay_percentage( + fault_delay); + fault_injection_policy_json["delayPercentageNumerator"] = + Json(envoy_type_v3_FractionalPercent_numerator(percent)); + fault_injection_policy_json["delayPercentageDenominator"] = + Json(GetDenominator(percent)); + } + // Section 3: Parse the maximum active faults + const auto* max_fault_wrapper = + envoy_extensions_filters_http_fault_v3_HTTPFault_max_active_faults( + http_fault); + if (max_fault_wrapper != nullptr) { + fault_injection_policy_json["maxFaults"] = + google_protobuf_UInt32Value_value(max_fault_wrapper); + } + return fault_injection_policy_json; +} + +} // namespace + +void XdsHttpFaultFilter::PopulateSymtab(upb_symtab* symtab) const { + envoy_extensions_filters_http_fault_v3_HTTPFault_getmsgdef(symtab); +} + +absl::StatusOr<XdsHttpFilterImpl::FilterConfig> +XdsHttpFaultFilter::GenerateFilterConfig(upb_strview serialized_filter_config, + upb_arena* arena) const { + absl::StatusOr<Json> parse_result = + ParseHttpFaultIntoJson(serialized_filter_config, arena); + if (!parse_result.ok()) { + return parse_result.status(); + } + return FilterConfig{kXdsHttpFaultFilterConfigName, std::move(*parse_result)}; +} + +absl::StatusOr<XdsHttpFilterImpl::FilterConfig> +XdsHttpFaultFilter::GenerateFilterConfigOverride( + upb_strview serialized_filter_config, upb_arena* arena) const { + // HTTPFault filter has the same message type in HTTP connection manager's + // filter config and in overriding filter config field. + return GenerateFilterConfig(serialized_filter_config, arena); +} + +const grpc_channel_filter* XdsHttpFaultFilter::channel_filter() const { + return &FaultInjectionFilterVtable; +} + +grpc_channel_args* XdsHttpFaultFilter::ModifyChannelArgs( + grpc_channel_args* args) const { + grpc_arg args_to_add = grpc_channel_arg_integer_create( + const_cast<char*>(GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG), 1); + grpc_channel_args* new_args = + grpc_channel_args_copy_and_add(args, &args_to_add, 1); + // Since this function takes the ownership of the channel args, it needs to + // deallocate the old ones to prevent leak. + grpc_channel_args_destroy(args); + return new_args; +} + +absl::StatusOr<XdsHttpFilterImpl::ServiceConfigJsonEntry> +XdsHttpFaultFilter::GenerateServiceConfig( + const FilterConfig& hcm_filter_config, + const FilterConfig* filter_config_override) const { + Json policy_json = filter_config_override != nullptr + ? filter_config_override->config + : hcm_filter_config.config; + // The policy JSON may be empty, that's allowed. + return ServiceConfigJsonEntry{"faultInjectionPolicy", policy_json.Dump()}; +} + +} // namespace grpc_core |