1 //
2 // Copyright 2021 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include <grpc/support/port_platform.h>
18
19 #include "src/core/ext/xds/xds_http_fault_filter.h"
20
21 #include <grpc/grpc.h>
22
23 #include <string>
24
25 #include "absl/status/statusor.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/strings/str_format.h"
28 #include "absl/strings/string_view.h"
29 #include "envoy/extensions/filters/common/fault/v3/fault.upb.h"
30 #include "envoy/extensions/filters/http/fault/v3/fault.upb.h"
31 #include "envoy/extensions/filters/http/fault/v3/fault.upbdefs.h"
32 #include "envoy/type/v3/percent.upb.h"
33 #include "google/protobuf/any.upb.h"
34 #include "google/protobuf/duration.upb.h"
35 #include "google/protobuf/wrappers.upb.h"
36 #include "src/core/ext/filters/fault_injection/fault_injection_filter.h"
37 #include "src/core/ext/xds/xds_http_filters.h"
38 #include "src/core/lib/channel/channel_args.h"
39 #include "src/core/lib/channel/channel_stack.h"
40 #include "src/core/lib/channel/status_util.h"
41 #include "src/core/lib/json/json.h"
42 #include "src/core/lib/transport/status_conversion.h"
43 #include "upb/def.h"
44
45 namespace grpc_core {
46
47 const char* kXdsHttpFaultFilterConfigName =
48 "envoy.extensions.filters.http.fault.v3.HTTPFault";
49
50 namespace {
51
GetDenominator(const envoy_type_v3_FractionalPercent * fraction)52 uint32_t GetDenominator(const envoy_type_v3_FractionalPercent* fraction) {
53 if (fraction != nullptr) {
54 const auto denominator =
55 static_cast<envoy_type_v3_FractionalPercent_DenominatorType>(
56 envoy_type_v3_FractionalPercent_denominator(fraction));
57 switch (denominator) {
58 case envoy_type_v3_FractionalPercent_MILLION:
59 return 1000000;
60 case envoy_type_v3_FractionalPercent_TEN_THOUSAND:
61 return 10000;
62 case envoy_type_v3_FractionalPercent_HUNDRED:
63 default:
64 return 100;
65 }
66 }
67 // Use 100 as the default denominator
68 return 100;
69 }
70
ParseHttpFaultIntoJson(upb_strview serialized_http_fault,upb_arena * arena)71 absl::StatusOr<Json> ParseHttpFaultIntoJson(upb_strview serialized_http_fault,
72 upb_arena* arena) {
73 auto* http_fault = envoy_extensions_filters_http_fault_v3_HTTPFault_parse(
74 serialized_http_fault.data, serialized_http_fault.size, arena);
75 if (http_fault == nullptr) {
76 return absl::InvalidArgumentError(
77 "could not parse fault injection filter config");
78 }
79 // NOTE(lidiz): Here, we are manually translating the upb messages into the
80 // JSON form of the filter config as part of method config, which will be
81 // directly used later by service config. In this way, we can validate the
82 // filter configs, and NACK if needed. It also allows the service config to
83 // function independently without xDS, but not the other way around.
84 // NOTE(lidiz): please refer to FaultInjectionPolicy for ground truth
85 // definitions, located at:
86 // src/core/ext/filters/fault_injection/service_config_parser.h
87 Json::Object fault_injection_policy_json;
88 // Section 1: Parse the abort injection config
89 const auto* fault_abort =
90 envoy_extensions_filters_http_fault_v3_HTTPFault_abort(http_fault);
91 if (fault_abort != nullptr) {
92 grpc_status_code abort_grpc_status_code = GRPC_STATUS_OK;
93 // Try if gRPC status code is set first
94 int abort_grpc_status_code_raw =
95 envoy_extensions_filters_http_fault_v3_FaultAbort_grpc_status(
96 fault_abort);
97 if (abort_grpc_status_code_raw != 0) {
98 if (!grpc_status_code_from_int(abort_grpc_status_code_raw,
99 &abort_grpc_status_code)) {
100 return absl::InvalidArgumentError(absl::StrCat(
101 "invalid gRPC status code: ", abort_grpc_status_code_raw));
102 }
103 } else {
104 // if gRPC status code is empty, check http status
105 int abort_http_status_code =
106 envoy_extensions_filters_http_fault_v3_FaultAbort_http_status(
107 fault_abort);
108 if (abort_http_status_code != 0 and abort_http_status_code != 200) {
109 abort_grpc_status_code =
110 grpc_http2_status_to_grpc_status(abort_http_status_code);
111 }
112 }
113 // Set the abort_code, even if it's OK
114 fault_injection_policy_json["abortCode"] =
115 grpc_status_code_to_string(abort_grpc_status_code);
116 // Set the headers if we enabled header abort injection control
117 if (envoy_extensions_filters_http_fault_v3_FaultAbort_has_header_abort(
118 fault_abort)) {
119 fault_injection_policy_json["abortCodeHeader"] =
120 "x-envoy-fault-abort-grpc-request";
121 fault_injection_policy_json["abortPercentageHeader"] =
122 "x-envoy-fault-abort-percentage";
123 }
124 // Set the fraction percent
125 auto* percent =
126 envoy_extensions_filters_http_fault_v3_FaultAbort_percentage(
127 fault_abort);
128 fault_injection_policy_json["abortPercentageNumerator"] =
129 Json(envoy_type_v3_FractionalPercent_numerator(percent));
130 fault_injection_policy_json["abortPercentageDenominator"] =
131 Json(GetDenominator(percent));
132 }
133 // Section 2: Parse the delay injection config
134 const auto* fault_delay =
135 envoy_extensions_filters_http_fault_v3_HTTPFault_delay(http_fault);
136 if (fault_delay != nullptr) {
137 // Parse the delay duration
138 const auto* delay_duration =
139 envoy_extensions_filters_common_fault_v3_FaultDelay_fixed_delay(
140 fault_delay);
141 if (delay_duration != nullptr) {
142 fault_injection_policy_json["delay"] = absl::StrFormat(
143 "%d.%09ds", google_protobuf_Duration_seconds(delay_duration),
144 google_protobuf_Duration_nanos(delay_duration));
145 }
146 // Set the headers if we enabled header delay injection control
147 if (envoy_extensions_filters_common_fault_v3_FaultDelay_has_header_delay(
148 fault_delay)) {
149 fault_injection_policy_json["delayHeader"] =
150 "x-envoy-fault-delay-request";
151 fault_injection_policy_json["delayPercentageHeader"] =
152 "x-envoy-fault-delay-request-percentage";
153 }
154 // Set the fraction percent
155 auto* percent =
156 envoy_extensions_filters_common_fault_v3_FaultDelay_percentage(
157 fault_delay);
158 fault_injection_policy_json["delayPercentageNumerator"] =
159 Json(envoy_type_v3_FractionalPercent_numerator(percent));
160 fault_injection_policy_json["delayPercentageDenominator"] =
161 Json(GetDenominator(percent));
162 }
163 // Section 3: Parse the maximum active faults
164 const auto* max_fault_wrapper =
165 envoy_extensions_filters_http_fault_v3_HTTPFault_max_active_faults(
166 http_fault);
167 if (max_fault_wrapper != nullptr) {
168 fault_injection_policy_json["maxFaults"] =
169 google_protobuf_UInt32Value_value(max_fault_wrapper);
170 }
171 return fault_injection_policy_json;
172 }
173
174 } // namespace
175
PopulateSymtab(upb_symtab * symtab) const176 void XdsHttpFaultFilter::PopulateSymtab(upb_symtab* symtab) const {
177 envoy_extensions_filters_http_fault_v3_HTTPFault_getmsgdef(symtab);
178 }
179
180 absl::StatusOr<XdsHttpFilterImpl::FilterConfig>
GenerateFilterConfig(upb_strview serialized_filter_config,upb_arena * arena) const181 XdsHttpFaultFilter::GenerateFilterConfig(upb_strview serialized_filter_config,
182 upb_arena* arena) const {
183 absl::StatusOr<Json> parse_result =
184 ParseHttpFaultIntoJson(serialized_filter_config, arena);
185 if (!parse_result.ok()) {
186 return parse_result.status();
187 }
188 return FilterConfig{kXdsHttpFaultFilterConfigName, std::move(*parse_result)};
189 }
190
191 absl::StatusOr<XdsHttpFilterImpl::FilterConfig>
GenerateFilterConfigOverride(upb_strview serialized_filter_config,upb_arena * arena) const192 XdsHttpFaultFilter::GenerateFilterConfigOverride(
193 upb_strview serialized_filter_config, upb_arena* arena) const {
194 // HTTPFault filter has the same message type in HTTP connection manager's
195 // filter config and in overriding filter config field.
196 return GenerateFilterConfig(serialized_filter_config, arena);
197 }
198
channel_filter() const199 const grpc_channel_filter* XdsHttpFaultFilter::channel_filter() const {
200 return &FaultInjectionFilterVtable;
201 }
202
ModifyChannelArgs(grpc_channel_args * args) const203 grpc_channel_args* XdsHttpFaultFilter::ModifyChannelArgs(
204 grpc_channel_args* args) const {
205 grpc_arg args_to_add = grpc_channel_arg_integer_create(
206 const_cast<char*>(GRPC_ARG_PARSE_FAULT_INJECTION_METHOD_CONFIG), 1);
207 grpc_channel_args* new_args =
208 grpc_channel_args_copy_and_add(args, &args_to_add, 1);
209 // Since this function takes the ownership of the channel args, it needs to
210 // deallocate the old ones to prevent leak.
211 grpc_channel_args_destroy(args);
212 return new_args;
213 }
214
215 absl::StatusOr<XdsHttpFilterImpl::ServiceConfigJsonEntry>
GenerateServiceConfig(const FilterConfig & hcm_filter_config,const FilterConfig * filter_config_override) const216 XdsHttpFaultFilter::GenerateServiceConfig(
217 const FilterConfig& hcm_filter_config,
218 const FilterConfig* filter_config_override) const {
219 Json policy_json = filter_config_override != nullptr
220 ? filter_config_override->config
221 : hcm_filter_config.config;
222 // The policy JSON may be empty, that's allowed.
223 return ServiceConfigJsonEntry{"faultInjectionPolicy", policy_json.Dump()};
224 }
225
226 } // namespace grpc_core
227