1 // Copyright 2023 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <gmock/gmock.h>
16 #include <gtest/gtest.h>
17
18 #include <string>
19 #include <vector>
20
21 #include "absl/log/log.h"
22 #include "absl/strings/str_format.h"
23 #include "absl/strings/str_join.h"
24 #include "absl/strings/str_split.h"
25 #include "envoy/extensions/filters/http/stateful_session/v3/stateful_session.pb.h"
26 #include "envoy/extensions/http/stateful_session/cookie/v3/cookie.pb.h"
27 #include "src/core/config/config_vars.h"
28 #include "src/core/util/time.h"
29 #include "test/core/test_util/scoped_env_var.h"
30 #include "test/cpp/end2end/xds/xds_end2end_test_lib.h"
31
32 namespace grpc {
33 namespace testing {
34 namespace {
35 using ::envoy::config::core::v3::HealthStatus;
36 using ::envoy::config::route::v3::Route;
37 using ::envoy::extensions::filters::http::stateful_session::v3::StatefulSession;
38 using ::envoy::extensions::filters::http::stateful_session::v3::
39 StatefulSessionPerRoute;
40 using ::envoy::extensions::filters::network::http_connection_manager::v3::
41 HttpFilter;
42 using ::envoy::extensions::http::stateful_session::cookie::v3 ::
43 CookieBasedSessionState;
44
45 constexpr absl::string_view kCookieName = "grpc_session_cookie";
46 constexpr absl::string_view kFilterName = "envoy.stateful_session";
47
48 class OverrideHostTest : public XdsEnd2endTest {
49 protected:
50 struct Cookie {
51 std::string name;
52 std::string value;
53 std::set<std::string> attributes;
54
Headergrpc::testing::__anon516d0c2a0111::OverrideHostTest::Cookie55 std::pair<std::string, std::string> Header() const {
56 return std::make_pair("cookie", absl::StrFormat("%s=%s", name, value));
57 }
58
59 template <typename Sink>
AbslStringify(Sink & sink,const Cookie & cookie)60 friend void AbslStringify(Sink& sink, const Cookie& cookie) {
61 absl::Format(&sink, "(Cookie: %s, value: %s, attributes: {%s})",
62 cookie.name, cookie.value,
63 absl::StrJoin(cookie.attributes, ", "));
64 }
65 };
66
ParseCookie(absl::string_view header)67 static Cookie ParseCookie(absl::string_view header) {
68 Cookie cookie;
69 std::pair<absl::string_view, absl::string_view> name_value =
70 absl::StrSplit(header, absl::MaxSplits('=', 1));
71 cookie.name = std::string(name_value.first);
72 std::pair<absl::string_view, absl::string_view> value_attrs =
73 absl::StrSplit(name_value.second, absl::MaxSplits(';', 1));
74 cookie.value = std::string(value_attrs.first);
75 for (absl::string_view segment : absl::StrSplit(value_attrs.second, ';')) {
76 cookie.attributes.emplace(absl::StripAsciiWhitespace(segment));
77 }
78 return cookie;
79 }
80
GetCookies(const std::multimap<std::string,std::string> & server_initial_metadata)81 static std::vector<Cookie> GetCookies(
82 const std::multimap<std::string, std::string>& server_initial_metadata) {
83 std::vector<Cookie> values;
84 auto pair = server_initial_metadata.equal_range("set-cookie");
85 for (auto it = pair.first; it != pair.second; ++it) {
86 std::pair<absl::string_view, absl::string_view> key_value =
87 absl::StrSplit(it->second, '=');
88 std::pair<absl::string_view, absl::string_view> key_value2 =
89 absl::StrSplit(key_value.second, ';');
90 std::string decoded;
91 EXPECT_TRUE(absl::Base64Unescape(key_value2.first, &decoded));
92 LOG(INFO) << "set-cookie header: " << it->second
93 << " (decoded: " << decoded << ")";
94 values.emplace_back(ParseCookie(it->second));
95 EXPECT_FALSE(values.back().value.empty());
96 EXPECT_THAT(values.back().attributes, ::testing::Contains("HttpOnly"));
97 }
98 return values;
99 }
100
101 // Builds a Listener with Fault Injection filter config. If the http_fault
102 // is nullptr, then assign an empty filter config. This filter config is
103 // required to enable the fault injection features.
BuildListenerWithStatefulSessionFilter(absl::string_view cookie_name=kCookieName)104 Listener BuildListenerWithStatefulSessionFilter(
105 absl::string_view cookie_name = kCookieName) {
106 StatefulSession stateful_session;
107 if (!cookie_name.empty()) {
108 CookieBasedSessionState cookie_state;
109 cookie_state.mutable_cookie()->set_name(std::string(cookie_name));
110 stateful_session.mutable_session_state()
111 ->mutable_typed_config()
112 ->PackFrom(cookie_state);
113 }
114 // HttpConnectionManager http_connection_manager;
115 Listener listener = default_listener_;
116 HttpConnectionManager http_connection_manager =
117 ClientHcmAccessor().Unpack(listener);
118 // Insert new filter ahead of the existing router filter.
119 HttpFilter* session_filter =
120 http_connection_manager.mutable_http_filters(0);
121 *http_connection_manager.add_http_filters() = *session_filter;
122 session_filter->set_name(kFilterName);
123 session_filter->mutable_typed_config()->PackFrom(stateful_session);
124 ClientHcmAccessor().Pack(http_connection_manager, &listener);
125 return listener;
126 }
127
GetCookiesForBackend(grpc_core::DebugLocation debug_location,size_t backend_index,size_t max_requests_per_backend=1,const RpcOptions & options=RpcOptions ())128 std::vector<Cookie> GetCookiesForBackend(
129 grpc_core::DebugLocation debug_location, size_t backend_index,
130 size_t max_requests_per_backend = 1,
131 const RpcOptions& options = RpcOptions()) {
132 EXPECT_LT(backend_index, backends_.size());
133 if (backend_index >= backends_.size()) {
134 return {};
135 }
136 const auto& backend = backends_[backend_index];
137 for (size_t i = 0; i < max_requests_per_backend * backends_.size(); ++i) {
138 std::multimap<std::string, std::string> server_initial_metadata;
139 grpc::Status status = SendRpc(options, nullptr, &server_initial_metadata);
140 EXPECT_TRUE(status.ok())
141 << "code=" << status.error_code()
142 << ", message=" << status.error_message() << "\n"
143 << debug_location.file() << ":" << debug_location.line();
144 if (!status.ok()) {
145 return {};
146 }
147 size_t count = backend->backend_service()->request_count() +
148 backend->backend_service1()->request_count() +
149 backend->backend_service2()->request_count();
150 ResetBackendCounters();
151 if (count == 1) {
152 return GetCookies(server_initial_metadata);
153 }
154 }
155 ADD_FAILURE_AT(debug_location.file(), debug_location.line())
156 << "Desired backend had not been hit";
157 return {};
158 }
159
160 // Send requests until a desired backend is hit and returns cookie name/value
161 // pairs. Empty collection is returned if the backend was never hit.
162 // For weighted clusters, more than one request per backend may be necessary
163 // to obtain the cookie. max_requests_per_backend argument specifies
164 // the number of requests per backend to send.
165 absl::optional<std::pair<std::string, std::string>>
GetAffinityCookieHeaderForBackend(grpc_core::DebugLocation debug_location,size_t backend_index,size_t max_requests_per_backend=1,const RpcOptions & options=RpcOptions (),absl::string_view cookie_name=kCookieName)166 GetAffinityCookieHeaderForBackend(
167 grpc_core::DebugLocation debug_location, size_t backend_index,
168 size_t max_requests_per_backend = 1,
169 const RpcOptions& options = RpcOptions(),
170 absl::string_view cookie_name = kCookieName) {
171 auto cookies = GetCookiesForBackend(debug_location, backend_index,
172 max_requests_per_backend, options);
173 for (const auto& cookie : cookies) {
174 if (cookie.name == cookie_name) {
175 return cookie.Header();
176 }
177 }
178 return absl::nullopt;
179 }
180
SetClusterResource(absl::string_view cluster_name,absl::string_view eds_resource_name)181 void SetClusterResource(absl::string_view cluster_name,
182 absl::string_view eds_resource_name) {
183 Cluster cluster = default_cluster_;
184 cluster.set_name(cluster_name);
185 cluster.mutable_eds_cluster_config()->set_service_name(eds_resource_name);
186 balancer_->ads_service()->SetCdsResource(cluster);
187 }
188
BuildRouteConfigurationWithWeightedClusters(const std::map<absl::string_view,uint32_t> & clusters)189 RouteConfiguration BuildRouteConfigurationWithWeightedClusters(
190 const std::map<absl::string_view, uint32_t>& clusters) {
191 RouteConfiguration new_route_config = default_route_config_;
192 auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
193 for (const auto& cluster : clusters) {
194 auto* weighted_cluster =
195 route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
196 weighted_cluster->set_name(cluster.first);
197 weighted_cluster->mutable_weight()->set_value(cluster.second);
198 }
199 return new_route_config;
200 }
201
SetCdsAndEdsResources(absl::string_view cluster_name,absl::string_view eds_service_name,size_t start_index,size_t end_index)202 void SetCdsAndEdsResources(absl::string_view cluster_name,
203 absl::string_view eds_service_name,
204 size_t start_index, size_t end_index) {
205 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
206 EdsResourceArgs({{"locality0",
207 CreateEndpointsForBackends(start_index, end_index)}}),
208 eds_service_name));
209 SetClusterResource(cluster_name, eds_service_name);
210 }
211
BackendRequestPercentage(const std::unique_ptr<BackendServerThread> & backend,size_t num_requests)212 static double BackendRequestPercentage(
213 const std::unique_ptr<BackendServerThread>& backend,
214 size_t num_requests) {
215 return static_cast<double>(backend->backend_service()->request_count()) /
216 num_requests;
217 }
218
BuildStatefulSessionRouteConfig(absl::string_view match_prefix,absl::string_view cookie_name,absl::optional<grpc_core::Duration> opt_duration=absl::nullopt)219 static Route BuildStatefulSessionRouteConfig(
220 absl::string_view match_prefix, absl::string_view cookie_name,
221 absl::optional<grpc_core::Duration> opt_duration = absl::nullopt) {
222 StatefulSessionPerRoute stateful_session_per_route;
223 if (!cookie_name.empty()) {
224 auto* session_state =
225 stateful_session_per_route.mutable_stateful_session()
226 ->mutable_session_state();
227 session_state->set_name("envoy.http.stateful_session.cookie");
228 CookieBasedSessionState cookie_config;
229 cookie_config.mutable_cookie()->set_name(cookie_name);
230 if (opt_duration.has_value()) {
231 cookie_config.mutable_cookie()->mutable_ttl()->set_seconds(
232 opt_duration->seconds());
233 }
234 session_state->mutable_typed_config()->PackFrom(cookie_config);
235 }
236 google::protobuf::Any any;
237 any.PackFrom(stateful_session_per_route);
238 Route route;
239 route.mutable_match()->set_prefix(match_prefix);
240 route.mutable_route()->set_cluster(kDefaultClusterName);
241 route.mutable_typed_per_filter_config()->emplace(kFilterName, any);
242 return route;
243 }
244
CookieNames(absl::Span<const Cookie> cookies)245 static std::string CookieNames(absl::Span<const Cookie> cookies) {
246 std::vector<absl::string_view> names;
247 for (const auto& cookie : cookies) {
248 names.emplace_back(cookie.name);
249 }
250 absl::c_sort(names);
251 return absl::StrJoin(names, ", ");
252 }
253 };
254
255 INSTANTIATE_TEST_SUITE_P(XdsTest, OverrideHostTest,
256 ::testing::Values(XdsTestType()), &XdsTestType::Name);
257
TEST_P(OverrideHostTest,HappyPath)258 TEST_P(OverrideHostTest, HappyPath) {
259 CreateAndStartBackends(2);
260 SetListenerAndRouteConfiguration(balancer_.get(),
261 BuildListenerWithStatefulSessionFilter(),
262 default_route_config_);
263 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
264 EdsResourceArgs({{"locality0",
265 {CreateEndpoint(0, HealthStatus::HEALTHY),
266 CreateEndpoint(1, HealthStatus::UNKNOWN)}}})));
267 WaitForAllBackends(DEBUG_LOCATION);
268 // Get cookie for backend #0.
269 auto cookies = GetCookiesForBackend(DEBUG_LOCATION, 0);
270 ASSERT_THAT(cookies,
271 ::testing::ElementsAre(::testing::AllOf(
272 ::testing::Field("name", &Cookie::name, kCookieName),
273 ::testing::Field("attributes", &Cookie::attributes,
274 ::testing::ElementsAre("HttpOnly")),
275 ::testing::Field("value", &Cookie::value,
276 ::testing::Not(::testing::IsEmpty())))));
277 // All requests go to the backend we specified
278 CheckRpcSendOk(DEBUG_LOCATION, 5,
279 RpcOptions().set_metadata({cookies.front().Header()}));
280 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
281 // Round-robin spreads the load
282 ResetBackendCounters();
283 CheckRpcSendOk(DEBUG_LOCATION, backends_.size() * 2);
284 EXPECT_EQ(2, backends_[0]->backend_service()->request_count());
285 EXPECT_EQ(2, backends_[1]->backend_service()->request_count());
286 // Call a different service with the same cookie
287 ResetBackendCounters();
288 CheckRpcSendOk(DEBUG_LOCATION, 5,
289 RpcOptions()
290 .set_metadata({cookies.front().Header()})
291 .set_rpc_service(RpcService::SERVICE_ECHO2));
292 EXPECT_EQ(backends_[0]->backend_service2()->request_count(), 5);
293 }
294
TEST_P(OverrideHostTest,AffinityWorksAcrossPriorities)295 TEST_P(OverrideHostTest, AffinityWorksAcrossPriorities) {
296 CreateAndStartBackends(3);
297 SetListenerAndRouteConfiguration(balancer_.get(),
298 BuildListenerWithStatefulSessionFilter(),
299 default_route_config_);
300 // Locality 0 contains backends 0 and 1. We start with this locality
301 // in priority 0.
302 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
303 EdsResourceArgs({{"locality0", CreateEndpointsForBackends(0, 2)}})));
304 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
305 // Get cookie for backend 1.
306 auto cookies = GetCookiesForBackend(DEBUG_LOCATION, 1);
307 ASSERT_THAT(cookies,
308 ::testing::ElementsAre(::testing::AllOf(
309 ::testing::Field("name", &Cookie::name, kCookieName),
310 ::testing::Field("attributes", &Cookie::attributes,
311 ::testing::ElementsAre("HttpOnly")),
312 ::testing::Field("value", &Cookie::value,
313 ::testing::Not(::testing::IsEmpty())))));
314 // The cookie should send all traffic to backend 1.
315 CheckRpcSendOk(DEBUG_LOCATION, 5,
316 RpcOptions().set_metadata({cookies.front().Header()}));
317 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
318 // Send an update that moves locality 0 to priority 1.
319 // Add a new locality in priority 0 containing backend 2.
320 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({
321 {"locality1", CreateEndpointsForBackends(2, 3)},
322 {"locality0", CreateEndpointsForBackends(0, 2), kDefaultLocalityWeight,
323 /*priority=*/1},
324 })));
325 WaitForBackend(DEBUG_LOCATION, 2);
326 // Using the cookie should continue to send traffic to backend 1.
327 CheckRpcSendOk(DEBUG_LOCATION, 5,
328 RpcOptions().set_metadata({cookies.front().Header()}));
329 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
330 }
331
TEST_P(OverrideHostTest,AffinityWorksAcrossPrioritiesHeuristicChangesChildName)332 TEST_P(OverrideHostTest,
333 AffinityWorksAcrossPrioritiesHeuristicChangesChildName) {
334 CreateAndStartBackends(3);
335 SetListenerAndRouteConfiguration(balancer_.get(),
336 BuildListenerWithStatefulSessionFilter(),
337 default_route_config_);
338 // Priority 0:
339 // - locality 0: backend 0
340 // - locality 1: backend 1
341 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({
342 {"locality0", CreateEndpointsForBackends(0, 1)},
343 {"locality1", CreateEndpointsForBackends(1, 2)},
344 })));
345 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
346 // Get cookie for backend 1.
347 // It may take more requests than usual to hit the backend we want,
348 // since the weighted_target policy does not do a strict round-robin.
349 auto cookies =
350 GetCookiesForBackend(DEBUG_LOCATION, 1, /*max_requests_per_backend=*/10);
351 ASSERT_THAT(cookies,
352 ::testing::ElementsAre(::testing::AllOf(
353 ::testing::Field("name", &Cookie::name, kCookieName),
354 ::testing::Field("attributes", &Cookie::attributes,
355 ::testing::ElementsAre("HttpOnly")),
356 ::testing::Field("value", &Cookie::value,
357 ::testing::Not(::testing::IsEmpty())))));
358 // The cookie should send all traffic to backend 1.
359 CheckRpcSendOk(DEBUG_LOCATION, 5,
360 RpcOptions().set_metadata({cookies.front().Header()}));
361 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
362 // Priority 0:
363 // - locality 0: backend 0
364 // - locality 2: backend 2
365 // Priority 1:
366 // - locality 1: backend 1
367 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({
368 {"locality0", CreateEndpointsForBackends(0, 1)},
369 {"locality2", CreateEndpointsForBackends(2, 3)},
370 {"locality1", CreateEndpointsForBackends(1, 2), kDefaultLocalityWeight,
371 /*priority=*/1},
372 })));
373 WaitForBackend(DEBUG_LOCATION, 2);
374 // Using the cookie should continue to send traffic to backend 1.
375 CheckRpcSendOk(DEBUG_LOCATION, 5,
376 RpcOptions().set_metadata({cookies.front().Header()}));
377 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
378 }
379
TEST_P(OverrideHostTest,DrainingIncludedFromOverrideSet)380 TEST_P(OverrideHostTest, DrainingIncludedFromOverrideSet) {
381 CreateAndStartBackends(3);
382 Cluster cluster = default_cluster_;
383 auto* lb_config = cluster.mutable_common_lb_config();
384 auto* override_health_status_set = lb_config->mutable_override_host_status();
385 override_health_status_set->add_statuses(HealthStatus::HEALTHY);
386 override_health_status_set->add_statuses(HealthStatus::UNKNOWN);
387 override_health_status_set->add_statuses(HealthStatus::DRAINING);
388 balancer_->ads_service()->SetCdsResource(cluster);
389 SetListenerAndRouteConfiguration(balancer_.get(),
390 BuildListenerWithStatefulSessionFilter(),
391 default_route_config_);
392 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
393 EdsResourceArgs({{"locality0",
394 {CreateEndpoint(0, HealthStatus::HEALTHY),
395 CreateEndpoint(1, HealthStatus::HEALTHY)}}})));
396 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
397 CheckRpcSendOk(DEBUG_LOCATION, 4);
398 EXPECT_EQ(2, backends_[0]->backend_service()->request_count());
399 EXPECT_EQ(2, backends_[1]->backend_service()->request_count());
400 EXPECT_EQ(0, backends_[2]->backend_service()->request_count());
401 ResetBackendCounters();
402 // Get cookie for backend #0.
403 auto session_cookie = GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 0);
404 ASSERT_TRUE(session_cookie.has_value());
405 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
406 EdsResourceArgs({{"locality0",
407 {CreateEndpoint(0, HealthStatus::DRAINING),
408 CreateEndpoint(1, HealthStatus::HEALTHY),
409 CreateEndpoint(2, HealthStatus::HEALTHY)}}})));
410 WaitForAllBackends(DEBUG_LOCATION, 2);
411 // Draining subchannel works when used as an override host.
412 CheckRpcSendOk(DEBUG_LOCATION, 4,
413 RpcOptions().set_metadata({*session_cookie}));
414 EXPECT_EQ(4, backends_[0]->backend_service()->request_count());
415 EXPECT_EQ(0, backends_[1]->backend_service()->request_count());
416 EXPECT_EQ(0, backends_[2]->backend_service()->request_count());
417 ResetBackendCounters();
418 // Round robin does not see the draining backend
419 CheckRpcSendOk(DEBUG_LOCATION, 4);
420 EXPECT_EQ(0, backends_[0]->backend_service()->request_count());
421 EXPECT_EQ(2, backends_[1]->backend_service()->request_count());
422 EXPECT_EQ(2, backends_[2]->backend_service()->request_count());
423 ResetBackendCounters();
424 }
425
TEST_P(OverrideHostTest,DrainingExcludedFromOverrideSet)426 TEST_P(OverrideHostTest, DrainingExcludedFromOverrideSet) {
427 CreateAndStartBackends(3);
428 Cluster cluster = default_cluster_;
429 auto* lb_config = cluster.mutable_common_lb_config();
430 auto* override_health_status_set = lb_config->mutable_override_host_status();
431 override_health_status_set->add_statuses(HealthStatus::HEALTHY);
432 override_health_status_set->add_statuses(HealthStatus::UNKNOWN);
433 balancer_->ads_service()->SetCdsResource(cluster);
434 SetListenerAndRouteConfiguration(balancer_.get(),
435 BuildListenerWithStatefulSessionFilter(),
436 default_route_config_);
437 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
438 EdsResourceArgs({{"locality0",
439 {CreateEndpoint(0, HealthStatus::HEALTHY),
440 CreateEndpoint(1, HealthStatus::HEALTHY)}}})));
441 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
442 CheckRpcSendOk(DEBUG_LOCATION, 4);
443 EXPECT_EQ(2, backends_[0]->backend_service()->request_count());
444 EXPECT_EQ(2, backends_[1]->backend_service()->request_count());
445 EXPECT_EQ(0, backends_[2]->backend_service()->request_count());
446 ResetBackendCounters();
447 // Get a cookie for backends_[0].
448 auto session_cookie = GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 0);
449 ASSERT_TRUE(session_cookie.has_value());
450 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
451 EdsResourceArgs({{"locality0",
452 {CreateEndpoint(0, HealthStatus::DRAINING),
453 CreateEndpoint(1, HealthStatus::HEALTHY),
454 CreateEndpoint(2, HealthStatus::UNKNOWN)}}})));
455 WaitForAllBackends(DEBUG_LOCATION, 2);
456 // Override for the draining host is not honored, RR is used instead.
457 CheckRpcSendOk(DEBUG_LOCATION, 4,
458 RpcOptions().set_metadata({*session_cookie}));
459 EXPECT_EQ(0, backends_[0]->backend_service()->request_count());
460 EXPECT_EQ(2, backends_[1]->backend_service()->request_count());
461 EXPECT_EQ(2, backends_[2]->backend_service()->request_count());
462 ResetBackendCounters();
463 }
464
TEST_P(OverrideHostTest,UnhealthyEndpoint)465 TEST_P(OverrideHostTest, UnhealthyEndpoint) {
466 CreateAndStartBackends(3);
467 Cluster cluster = default_cluster_;
468 auto* lb_config = cluster.mutable_common_lb_config();
469 auto* override_health_status_set = lb_config->mutable_override_host_status();
470 override_health_status_set->add_statuses(HealthStatus::HEALTHY);
471 override_health_status_set->add_statuses(HealthStatus::DRAINING);
472 override_health_status_set->add_statuses(HealthStatus::DEGRADED);
473 override_health_status_set->add_statuses(HealthStatus::UNKNOWN);
474 balancer_->ads_service()->SetCdsResource(cluster);
475 SetListenerAndRouteConfiguration(balancer_.get(),
476 BuildListenerWithStatefulSessionFilter(),
477 default_route_config_);
478 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
479 EdsResourceArgs({{"locality0",
480 {CreateEndpoint(0, HealthStatus::HEALTHY),
481 CreateEndpoint(1, HealthStatus::HEALTHY)}}})));
482 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
483 CheckRpcSendOk(DEBUG_LOCATION, 4);
484 EXPECT_EQ(2, backends_[0]->backend_service()->request_count());
485 EXPECT_EQ(2, backends_[1]->backend_service()->request_count());
486 EXPECT_EQ(0, backends_[2]->backend_service()->request_count());
487 ResetBackendCounters();
488 // Get a cookie for backends_[0].
489 auto session_cookie = GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 0);
490 ASSERT_TRUE(session_cookie.has_value());
491 LOG(INFO) << session_cookie->first << " " << session_cookie->second;
492 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
493 EdsResourceArgs({{"locality0",
494 {CreateEndpoint(0, HealthStatus::UNHEALTHY),
495 CreateEndpoint(1, HealthStatus::HEALTHY),
496 CreateEndpoint(2, HealthStatus::HEALTHY)}}})));
497 WaitForAllBackends(DEBUG_LOCATION, 2);
498 // Override for the draining host is not honored, RR is used instead.
499 CheckRpcSendOk(DEBUG_LOCATION, 2,
500 RpcOptions().set_metadata({*session_cookie}));
501 EXPECT_EQ(0, backends_[0]->backend_service()->request_count());
502 EXPECT_EQ(1, backends_[1]->backend_service()->request_count());
503 EXPECT_EQ(1, backends_[2]->backend_service()->request_count());
504 ResetBackendCounters();
505 }
506
TEST_P(OverrideHostTest,OverrideWithWeightedClusters)507 TEST_P(OverrideHostTest, OverrideWithWeightedClusters) {
508 CreateAndStartBackends(3);
509 const char* kNewCluster1Name = "new_cluster_1";
510 const char* kNewEdsService1Name = "new_eds_service_name_1";
511 const char* kNewCluster2Name = "new_cluster_2";
512 const char* kNewEdsService2Name = "new_eds_service_name_2";
513 const uint32_t kWeight1 = 1;
514 const uint32_t kWeight2 = 3;
515 const double kErrorTolerance = 0.025;
516 const size_t kNumEchoRpcs = ComputeIdealNumRpcs(
517 static_cast<double>(kWeight1) / (kWeight1 + kWeight2), kErrorTolerance);
518 // Populate EDS and CDS resources.
519 SetCdsAndEdsResources(kNewCluster1Name, kNewEdsService1Name, 0, 1);
520 SetCdsAndEdsResources(kNewCluster2Name, kNewEdsService2Name, 1, 3);
521 // Populating Route Configurations for LDS.
522 SetListenerAndRouteConfiguration(
523 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
524 BuildRouteConfigurationWithWeightedClusters(
525 {{kNewCluster1Name, kWeight1}, {kNewCluster2Name, kWeight2}}));
526 WaitForAllBackends(DEBUG_LOCATION, 0, 3);
527 // Get cookie
528 auto session_cookie =
529 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 3);
530 ASSERT_TRUE(session_cookie.has_value());
531 // All requests go to the backend we requested.
532 CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs,
533 RpcOptions().set_metadata({*session_cookie}));
534 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 0);
535 EXPECT_EQ(backends_[1]->backend_service()->request_count(), kNumEchoRpcs);
536 EXPECT_EQ(backends_[2]->backend_service()->request_count(), 0);
537 }
538
TEST_P(OverrideHostTest,ClusterOverrideHonoredButHostGone)539 TEST_P(OverrideHostTest, ClusterOverrideHonoredButHostGone) {
540 CreateAndStartBackends(4);
541 const char* kNewCluster1Name = "new_cluster_1";
542 const char* kNewEdsService1Name = "new_eds_service_name_1";
543 const char* kNewCluster2Name = "new_cluster_2";
544 const char* kNewEdsService2Name = "new_eds_service_name_2";
545 const uint32_t kWeight1 = 1;
546 const uint32_t kWeight2 = 3;
547 const double kErrorTolerance = 0.025;
548 const double kWeight2Percent =
549 static_cast<double>(kWeight2) / (kWeight1 + kWeight2);
550 const size_t kNumEchoRpcs =
551 ComputeIdealNumRpcs(kWeight2Percent, kErrorTolerance);
552 // Populate EDS and CDS resources.
553 SetCdsAndEdsResources(kNewCluster1Name, kNewEdsService1Name, 0, 1);
554 SetCdsAndEdsResources(kNewCluster2Name, kNewEdsService2Name, 1, 3);
555 // Populating Route Configurations for LDS.
556 SetListenerAndRouteConfiguration(
557 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
558 BuildRouteConfigurationWithWeightedClusters(
559 {{kNewCluster1Name, kWeight1}, {kNewCluster2Name, kWeight2}}));
560 WaitForAllBackends(DEBUG_LOCATION, 0, 3);
561 auto session_cookie =
562 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 4);
563 ASSERT_TRUE(session_cookie.has_value());
564 // Remove backends[1] from cluster2
565 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
566 EdsResourceArgs({{"locality0", CreateEndpointsForBackends(2, 4)}}),
567 kNewEdsService2Name));
568 WaitForAllBackends(DEBUG_LOCATION, 3, 4);
569 CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs,
570 RpcOptions().set_metadata({*session_cookie}));
571 // Traffic goes to a second cluster, where it is equally distributed between
572 // the two remaining hosts
573 EXPECT_THAT(BackendRequestPercentage(backends_[2], kNumEchoRpcs),
574 ::testing::DoubleNear(.5, kErrorTolerance));
575 EXPECT_THAT(BackendRequestPercentage(backends_[3], kNumEchoRpcs),
576 ::testing::DoubleNear(.5, kErrorTolerance));
577 EXPECT_NE(session_cookie, GetAffinityCookieHeaderForBackend(
578 DEBUG_LOCATION, 2, kNumEchoRpcs / 3));
579 }
580
TEST_P(OverrideHostTest,ClusterGoneHostStays)581 TEST_P(OverrideHostTest, ClusterGoneHostStays) {
582 CreateAndStartBackends(3);
583 const char* kNewCluster1Name = "new_cluster_1";
584 const char* kNewEdsService1Name = "new_eds_service_name_1";
585 const char* kNewCluster2Name = "new_cluster_2";
586 const char* kNewEdsService2Name = "new_eds_service_name_2";
587 const char* kNewCluster3Name = "new_cluster_3";
588 const char* kNewEdsService3Name = "new_eds_service_name_3";
589 const uint32_t kWeight1 = 1;
590 const uint32_t kWeight2 = 3;
591 const double kErrorTolerance = 0.025;
592 const double kPercentage1 =
593 static_cast<double>(kWeight1) / (kWeight1 + kWeight2);
594 const size_t kNumEchoRpcs =
595 ComputeIdealNumRpcs(kPercentage1, kErrorTolerance);
596 // Populate EDS and CDS resources.
597 SetCdsAndEdsResources(kNewCluster1Name, kNewEdsService1Name, 0, 1);
598 SetCdsAndEdsResources(kNewCluster2Name, kNewEdsService2Name, 1, 2);
599 // Populating Route Configurations for LDS.
600 SetListenerAndRouteConfiguration(
601 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
602 BuildRouteConfigurationWithWeightedClusters(
603 {{kNewCluster1Name, kWeight1}, {kNewCluster2Name, kWeight2}}));
604 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
605 auto backend1_in_cluster2_cookie =
606 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 3);
607 ASSERT_TRUE(backend1_in_cluster2_cookie.has_value());
608 // Create a new cluster, cluster 3, containing a new backend, backend 2.
609 SetCdsAndEdsResources(kNewCluster3Name, kNewEdsService3Name, 2, 3);
610 // Send an EDS update for cluster 1 that adds backend 1. (Now cluster 1 has
611 // backends 0 and 1.)
612 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
613 EdsResourceArgs({{"locality0", CreateEndpointsForBackends(0, 2)}}),
614 kNewEdsService1Name));
615 SetListenerAndRouteConfiguration(
616 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
617 BuildRouteConfigurationWithWeightedClusters(
618 {{kNewCluster1Name, kWeight1}, {kNewCluster3Name, kWeight2}}));
619 WaitForAllBackends(DEBUG_LOCATION, 2);
620 CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs,
621 RpcOptions().set_metadata({*backend1_in_cluster2_cookie}));
622 // Traffic is split between clusters. Cluster1 traffic is sent to backends_[1]
623 EXPECT_THAT(BackendRequestPercentage(backends_[0], kNumEchoRpcs),
624 ::testing::DoubleNear(0, kErrorTolerance));
625 EXPECT_THAT(BackendRequestPercentage(backends_[1], kNumEchoRpcs),
626 ::testing::DoubleNear(kPercentage1, kErrorTolerance));
627 EXPECT_THAT(BackendRequestPercentage(backends_[2], kNumEchoRpcs),
628 ::testing::DoubleNear(1 - kPercentage1, kErrorTolerance));
629 // backends_[1] cookie is updated with a new cluster
630 EXPECT_NE(
631 backend1_in_cluster2_cookie,
632 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 3));
633 }
634
TEST_P(OverrideHostTest,EnabledInRouteConfig)635 TEST_P(OverrideHostTest, EnabledInRouteConfig) {
636 CreateAndStartBackends(2);
637 RouteConfiguration route_config = default_route_config_;
638 *route_config.mutable_virtual_hosts(0)->mutable_routes(0) =
639 BuildStatefulSessionRouteConfig("", kCookieName);
640 SetListenerAndRouteConfiguration(balancer_.get(),
641 BuildListenerWithStatefulSessionFilter(""),
642 route_config);
643 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs(
644 {{"locality0", {CreateEndpoint(0), CreateEndpoint(1)}}})));
645 WaitForAllBackends(DEBUG_LOCATION);
646 // Get cookie for backend #0.
647 auto session_cookie = GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 0);
648 ASSERT_TRUE(session_cookie.has_value());
649 // All requests go to the backend we specified
650 CheckRpcSendOk(DEBUG_LOCATION, 5,
651 RpcOptions().set_metadata({*session_cookie}));
652 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
653 }
654
TEST_P(OverrideHostTest,DifferentPerRoute)655 TEST_P(OverrideHostTest, DifferentPerRoute) {
656 constexpr absl::string_view kOverriddenCookie = "overridden-cookie-name";
657 CreateAndStartBackends(2);
658 RouteConfiguration route_config = default_route_config_;
659 *route_config.mutable_virtual_hosts(0)->mutable_routes(0) =
660 BuildStatefulSessionRouteConfig("/grpc.testing.EchoTestService/Echo1",
661 "");
662 *route_config.mutable_virtual_hosts(0)->add_routes() =
663 BuildStatefulSessionRouteConfig("/grpc.testing.EchoTestService/Echo2",
664 kOverriddenCookie);
665 *route_config.mutable_virtual_hosts(0)->add_routes() =
666 default_route_config_.virtual_hosts(0).routes(0);
667 SetListenerAndRouteConfiguration(
668 balancer_.get(), BuildListenerWithStatefulSessionFilter(), route_config);
669 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs(
670 {{"locality0", {CreateEndpoint(0), CreateEndpoint(1)}}})));
671 WaitForAllBackends(DEBUG_LOCATION);
672 // Disabled for "echo1" method
673 auto echo1_cookie = GetCookiesForBackend(
674 DEBUG_LOCATION, 0, 1, RpcOptions().set_rpc_method(METHOD_ECHO1));
675 ASSERT_EQ(CookieNames(echo1_cookie), "");
676 // Overridden for "echo2" method
677 auto echo2_cookie = GetCookiesForBackend(
678 DEBUG_LOCATION, 0, 1, RpcOptions().set_rpc_method(METHOD_ECHO2));
679 ASSERT_EQ(CookieNames(echo2_cookie), kOverriddenCookie);
680 // Default for "echo" method
681 auto echo_cookie = GetCookiesForBackend(
682 DEBUG_LOCATION, 0, 1, RpcOptions().set_rpc_method(METHOD_ECHO));
683 ASSERT_EQ(CookieNames(echo_cookie), kCookieName);
684 // Echo1 endpoint ignores cookies
685 CheckRpcSendOk(DEBUG_LOCATION, 6,
686 RpcOptions()
687 .set_metadata({
688 echo_cookie.front().Header(),
689 echo2_cookie.front().Header(),
690 })
691 .set_rpc_method(METHOD_ECHO1));
692 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 3);
693 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 3);
694 // Echo2 honours the overwritten cookie but not the cookie from the top-level
695 // config.
696 backends_[0]->backend_service()->ResetCounters();
697 backends_[1]->backend_service()->ResetCounters();
698 CheckRpcSendOk(DEBUG_LOCATION, 6,
699 RpcOptions()
700 .set_metadata({echo_cookie.front().Header()})
701 .set_rpc_method(METHOD_ECHO2));
702 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 3);
703 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 3);
704 backends_[0]->backend_service()->ResetCounters();
705 CheckRpcSendOk(DEBUG_LOCATION, 6,
706 RpcOptions()
707 .set_metadata({echo2_cookie.front().Header()})
708 .set_rpc_method(METHOD_ECHO2));
709 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 6);
710 // Echo honours the original cookie but not the override cookie
711 backends_[0]->backend_service()->ResetCounters();
712 CheckRpcSendOk(DEBUG_LOCATION, 6,
713 RpcOptions()
714 .set_metadata({echo_cookie.front().Header()})
715 .set_rpc_method(METHOD_ECHO));
716 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 6);
717 backends_[0]->backend_service()->ResetCounters();
718 backends_[1]->backend_service()->ResetCounters();
719 CheckRpcSendOk(DEBUG_LOCATION, 6,
720 RpcOptions()
721 .set_metadata({echo2_cookie.front().Header()})
722 .set_rpc_method(METHOD_ECHO));
723 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 3);
724 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 3);
725 }
726
TEST_P(OverrideHostTest,TTLSetsMaxAge)727 TEST_P(OverrideHostTest, TTLSetsMaxAge) {
728 CreateAndStartBackends(1);
729 RouteConfiguration route_config = default_route_config_;
730 *route_config.mutable_virtual_hosts(0)->mutable_routes(0) =
731 BuildStatefulSessionRouteConfig("", kCookieName,
732 grpc_core::Duration::Seconds(42));
733 SetListenerAndRouteConfiguration(balancer_.get(),
734 BuildListenerWithStatefulSessionFilter(""),
735 route_config);
736 balancer_->ads_service()->SetEdsResource(
737 BuildEdsResource(EdsResourceArgs({{"locality0", {CreateEndpoint(0)}}})));
738 WaitForAllBackends(DEBUG_LOCATION);
739 // Get cookie for backend #0.
740 auto cookies = GetCookiesForBackend(DEBUG_LOCATION, 0);
741 ASSERT_EQ(cookies.size(), 1);
742 EXPECT_THAT(cookies.front().attributes,
743 ::testing::UnorderedElementsAre("Max-Age=42", "HttpOnly"));
744 }
745
TEST_P(OverrideHostTest,MultipleAddressesPerEndpoint)746 TEST_P(OverrideHostTest, MultipleAddressesPerEndpoint) {
747 // Create 3 backends, but leave backend 0 unstarted.
748 CreateBackends(3);
749 StartBackend(1);
750 StartBackend(2);
751 SetListenerAndRouteConfiguration(balancer_.get(),
752 BuildListenerWithStatefulSessionFilter(),
753 default_route_config_);
754 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
755 EdsResourceArgs({{"locality0",
756 {CreateEndpoint(0, HealthStatus::HEALTHY, 1, {1}),
757 CreateEndpoint(2, HealthStatus::UNKNOWN)}}})));
758 WaitForAllBackends(DEBUG_LOCATION, 1); // Wait for backends 1 and 2.
759 // Requests without a cookie should get round-robin across backends 1 and 2.
760 CheckRpcSendOk(DEBUG_LOCATION, 10);
761 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 0);
762 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
763 EXPECT_EQ(backends_[2]->backend_service()->request_count(), 5);
764 ResetBackendCounters();
765 // Get cookie for backend 1.
766 auto cookies = GetCookiesForBackend(DEBUG_LOCATION, 1);
767 ASSERT_THAT(cookies,
768 ::testing::ElementsAre(::testing::AllOf(
769 ::testing::Field("name", &Cookie::name, kCookieName),
770 ::testing::Field("attributes", &Cookie::attributes,
771 ::testing::ElementsAre("HttpOnly")),
772 ::testing::Field("value", &Cookie::value,
773 ::testing::Not(::testing::IsEmpty())))));
774 // Requests with the cookie always go to the same backend.
775 CheckRpcSendOk(DEBUG_LOCATION, 5,
776 RpcOptions().set_metadata({cookies.front().Header()}));
777 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
778 // Now start backend 0 and stop backend 1.
779 StartBackend(0);
780 ShutdownBackend(1);
781 // Wait for traffic to go to backend 0.
782 WaitForBackend(DEBUG_LOCATION, 0);
783 // Requests with no cookie should get round-robin across backends 0 and 2.
784 CheckRpcSendOk(DEBUG_LOCATION, 10);
785 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
786 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 0);
787 EXPECT_EQ(backends_[2]->backend_service()->request_count(), 5);
788 ResetBackendCounters();
789 // Requests with the same cookie should now go to backend 0.
790 CheckRpcSendOk(DEBUG_LOCATION, 5,
791 RpcOptions().set_metadata({cookies.front().Header()}));
792 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
793 }
794
795 } // namespace
796 } // namespace testing
797 } // namespace grpc
798
main(int argc,char ** argv)799 int main(int argc, char** argv) {
800 grpc::testing::TestEnvironment env(&argc, argv);
801 ::testing::InitGoogleTest(&argc, argv);
802 // Make the backup poller poll very frequently in order to pick up
803 // updates from all the subchannels's FDs.
804 grpc_core::ConfigVars::Overrides overrides;
805 overrides.client_channel_backup_poll_interval_ms = 1;
806 grpc_core::ConfigVars::SetOverrides(overrides);
807 #if TARGET_OS_IPHONE
808 // Workaround Apple CFStream bug
809 grpc_core::SetEnv("grpc_cfstream", "0");
810 #endif
811 grpc_init();
812 const auto result = RUN_ALL_TESTS();
813 grpc_shutdown();
814 return result;
815 }
816