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 <string>
16 #include <vector>
17
18 #include <gmock/gmock.h>
19 #include <gtest/gtest.h>
20
21 #include "absl/strings/str_format.h"
22 #include "absl/strings/str_join.h"
23 #include "absl/strings/str_split.h"
24
25 #include "src/core/lib/config/config_vars.h"
26 #include "src/core/lib/gprpp/time.h"
27 #include "src/proto/grpc/testing/xds/v3/stateful_session.pb.h"
28 #include "src/proto/grpc/testing/xds/v3/stateful_session_cookie.pb.h"
29 #include "test/core/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::__anon5c1c47e60111::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 gpr_log(GPR_INFO, "set-cookie header: %s (decoded: %s)",
93 std::string(it->second).c_str(), decoded.c_str());
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,OverrideWithWeightedClusters)465 TEST_P(OverrideHostTest, OverrideWithWeightedClusters) {
466 CreateAndStartBackends(3);
467 const char* kNewCluster1Name = "new_cluster_1";
468 const char* kNewEdsService1Name = "new_eds_service_name_1";
469 const char* kNewCluster2Name = "new_cluster_2";
470 const char* kNewEdsService2Name = "new_eds_service_name_2";
471 const uint32_t kWeight1 = 1;
472 const uint32_t kWeight2 = 3;
473 const double kErrorTolerance = 0.025;
474 const size_t kNumEchoRpcs = ComputeIdealNumRpcs(
475 static_cast<double>(kWeight1) / (kWeight1 + kWeight2), kErrorTolerance);
476 // Populate EDS and CDS resources.
477 SetCdsAndEdsResources(kNewCluster1Name, kNewEdsService1Name, 0, 1);
478 SetCdsAndEdsResources(kNewCluster2Name, kNewEdsService2Name, 1, 3);
479 // Populating Route Configurations for LDS.
480 SetListenerAndRouteConfiguration(
481 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
482 BuildRouteConfigurationWithWeightedClusters(
483 {{kNewCluster1Name, kWeight1}, {kNewCluster2Name, kWeight2}}));
484 WaitForAllBackends(DEBUG_LOCATION, 0, 3);
485 // Get cookie
486 auto session_cookie =
487 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 3);
488 ASSERT_TRUE(session_cookie.has_value());
489 // All requests go to the backend we requested.
490 CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs,
491 RpcOptions().set_metadata({*session_cookie}));
492 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 0);
493 EXPECT_EQ(backends_[1]->backend_service()->request_count(), kNumEchoRpcs);
494 EXPECT_EQ(backends_[2]->backend_service()->request_count(), 0);
495 }
496
TEST_P(OverrideHostTest,ClusterOverrideHonoredButHostGone)497 TEST_P(OverrideHostTest, ClusterOverrideHonoredButHostGone) {
498 CreateAndStartBackends(4);
499 const char* kNewCluster1Name = "new_cluster_1";
500 const char* kNewEdsService1Name = "new_eds_service_name_1";
501 const char* kNewCluster2Name = "new_cluster_2";
502 const char* kNewEdsService2Name = "new_eds_service_name_2";
503 const uint32_t kWeight1 = 1;
504 const uint32_t kWeight2 = 3;
505 const double kErrorTolerance = 0.025;
506 const double kWeight2Percent =
507 static_cast<double>(kWeight2) / (kWeight1 + kWeight2);
508 const size_t kNumEchoRpcs =
509 ComputeIdealNumRpcs(kWeight2Percent, kErrorTolerance);
510 // Populate EDS and CDS resources.
511 SetCdsAndEdsResources(kNewCluster1Name, kNewEdsService1Name, 0, 1);
512 SetCdsAndEdsResources(kNewCluster2Name, kNewEdsService2Name, 1, 3);
513 // Populating Route Configurations for LDS.
514 SetListenerAndRouteConfiguration(
515 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
516 BuildRouteConfigurationWithWeightedClusters(
517 {{kNewCluster1Name, kWeight1}, {kNewCluster2Name, kWeight2}}));
518 WaitForAllBackends(DEBUG_LOCATION, 0, 3);
519 auto session_cookie =
520 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 4);
521 ASSERT_TRUE(session_cookie.has_value());
522 // Remove backends[1] from cluster2
523 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
524 EdsResourceArgs({{"locality0", CreateEndpointsForBackends(2, 4)}}),
525 kNewEdsService2Name));
526 WaitForAllBackends(DEBUG_LOCATION, 3, 4);
527 CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs,
528 RpcOptions().set_metadata({*session_cookie}));
529 // Traffic goes to a second cluster, where it is equally distributed between
530 // the two remaining hosts
531 EXPECT_THAT(BackendRequestPercentage(backends_[2], kNumEchoRpcs),
532 ::testing::DoubleNear(.5, kErrorTolerance));
533 EXPECT_THAT(BackendRequestPercentage(backends_[3], kNumEchoRpcs),
534 ::testing::DoubleNear(.5, kErrorTolerance));
535 EXPECT_NE(session_cookie, GetAffinityCookieHeaderForBackend(
536 DEBUG_LOCATION, 2, kNumEchoRpcs / 3));
537 }
538
TEST_P(OverrideHostTest,ClusterGoneHostStays)539 TEST_P(OverrideHostTest, ClusterGoneHostStays) {
540 CreateAndStartBackends(3);
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 char* kNewCluster3Name = "new_cluster_3";
546 const char* kNewEdsService3Name = "new_eds_service_name_3";
547 const uint32_t kWeight1 = 1;
548 const uint32_t kWeight2 = 3;
549 const double kErrorTolerance = 0.025;
550 const double kPercentage1 =
551 static_cast<double>(kWeight1) / (kWeight1 + kWeight2);
552 const size_t kNumEchoRpcs =
553 ComputeIdealNumRpcs(kPercentage1, kErrorTolerance);
554 // Populate EDS and CDS resources.
555 SetCdsAndEdsResources(kNewCluster1Name, kNewEdsService1Name, 0, 1);
556 SetCdsAndEdsResources(kNewCluster2Name, kNewEdsService2Name, 1, 2);
557 // Populating Route Configurations for LDS.
558 SetListenerAndRouteConfiguration(
559 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
560 BuildRouteConfigurationWithWeightedClusters(
561 {{kNewCluster1Name, kWeight1}, {kNewCluster2Name, kWeight2}}));
562 WaitForAllBackends(DEBUG_LOCATION, 0, 2);
563 auto backend1_in_cluster2_cookie =
564 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 3);
565 ASSERT_TRUE(backend1_in_cluster2_cookie.has_value());
566 // Create a new cluster, cluster 3, containing a new backend, backend 2.
567 SetCdsAndEdsResources(kNewCluster3Name, kNewEdsService3Name, 2, 3);
568 // Send an EDS update for cluster 1 that adds backend 1. (Now cluster 1 has
569 // backends 0 and 1.)
570 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
571 EdsResourceArgs({{"locality0", CreateEndpointsForBackends(0, 2)}}),
572 kNewEdsService1Name));
573 SetListenerAndRouteConfiguration(
574 balancer_.get(), BuildListenerWithStatefulSessionFilter(),
575 BuildRouteConfigurationWithWeightedClusters(
576 {{kNewCluster1Name, kWeight1}, {kNewCluster3Name, kWeight2}}));
577 WaitForAllBackends(DEBUG_LOCATION, 2);
578 CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs,
579 RpcOptions().set_metadata({*backend1_in_cluster2_cookie}));
580 // Traffic is split between clusters. Cluster1 traffic is sent to backends_[1]
581 EXPECT_THAT(BackendRequestPercentage(backends_[0], kNumEchoRpcs),
582 ::testing::DoubleNear(0, kErrorTolerance));
583 EXPECT_THAT(BackendRequestPercentage(backends_[1], kNumEchoRpcs),
584 ::testing::DoubleNear(kPercentage1, kErrorTolerance));
585 EXPECT_THAT(BackendRequestPercentage(backends_[2], kNumEchoRpcs),
586 ::testing::DoubleNear(1 - kPercentage1, kErrorTolerance));
587 // backends_[1] cookie is updated with a new cluster
588 EXPECT_NE(
589 backend1_in_cluster2_cookie,
590 GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 1, kNumEchoRpcs / 3));
591 }
592
TEST_P(OverrideHostTest,EnabledInRouteConfig)593 TEST_P(OverrideHostTest, EnabledInRouteConfig) {
594 CreateAndStartBackends(2);
595 RouteConfiguration route_config = default_route_config_;
596 *route_config.mutable_virtual_hosts(0)->mutable_routes(0) =
597 BuildStatefulSessionRouteConfig("", kCookieName);
598 SetListenerAndRouteConfiguration(balancer_.get(),
599 BuildListenerWithStatefulSessionFilter(""),
600 route_config);
601 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs(
602 {{"locality0", {CreateEndpoint(0), CreateEndpoint(1)}}})));
603 WaitForAllBackends(DEBUG_LOCATION);
604 // Get cookie for backend #0.
605 auto session_cookie = GetAffinityCookieHeaderForBackend(DEBUG_LOCATION, 0);
606 ASSERT_TRUE(session_cookie.has_value());
607 // All requests go to the backend we specified
608 CheckRpcSendOk(DEBUG_LOCATION, 5,
609 RpcOptions().set_metadata({*session_cookie}));
610 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
611 }
612
TEST_P(OverrideHostTest,DifferentPerRoute)613 TEST_P(OverrideHostTest, DifferentPerRoute) {
614 constexpr absl::string_view kOverriddenCookie = "overridden-cookie-name";
615 CreateAndStartBackends(2);
616 RouteConfiguration route_config = default_route_config_;
617 *route_config.mutable_virtual_hosts(0)->mutable_routes(0) =
618 BuildStatefulSessionRouteConfig("/grpc.testing.EchoTestService/Echo1",
619 "");
620 *route_config.mutable_virtual_hosts(0)->add_routes() =
621 BuildStatefulSessionRouteConfig("/grpc.testing.EchoTestService/Echo2",
622 kOverriddenCookie);
623 *route_config.mutable_virtual_hosts(0)->add_routes() =
624 default_route_config_.virtual_hosts(0).routes(0);
625 SetListenerAndRouteConfiguration(
626 balancer_.get(), BuildListenerWithStatefulSessionFilter(), route_config);
627 balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs(
628 {{"locality0", {CreateEndpoint(0), CreateEndpoint(1)}}})));
629 WaitForAllBackends(DEBUG_LOCATION);
630 // Disabled for "echo1" method
631 auto echo1_cookie = GetCookiesForBackend(
632 DEBUG_LOCATION, 0, 1, RpcOptions().set_rpc_method(METHOD_ECHO1));
633 ASSERT_EQ(CookieNames(echo1_cookie), "");
634 // Overridden for "echo2" method
635 auto echo2_cookie = GetCookiesForBackend(
636 DEBUG_LOCATION, 0, 1, RpcOptions().set_rpc_method(METHOD_ECHO2));
637 ASSERT_EQ(CookieNames(echo2_cookie), kOverriddenCookie);
638 // Default for "echo" method
639 auto echo_cookie = GetCookiesForBackend(
640 DEBUG_LOCATION, 0, 1, RpcOptions().set_rpc_method(METHOD_ECHO));
641 ASSERT_EQ(CookieNames(echo_cookie), kCookieName);
642 // Echo1 endpoint ignores cookies
643 CheckRpcSendOk(DEBUG_LOCATION, 6,
644 RpcOptions()
645 .set_metadata({
646 echo_cookie.front().Header(),
647 echo2_cookie.front().Header(),
648 })
649 .set_rpc_method(METHOD_ECHO1));
650 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 3);
651 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 3);
652 // Echo2 honours the overwritten cookie but not the cookie from the top-level
653 // config.
654 backends_[0]->backend_service()->ResetCounters();
655 backends_[1]->backend_service()->ResetCounters();
656 CheckRpcSendOk(DEBUG_LOCATION, 6,
657 RpcOptions()
658 .set_metadata({echo_cookie.front().Header()})
659 .set_rpc_method(METHOD_ECHO2));
660 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 3);
661 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 3);
662 backends_[0]->backend_service()->ResetCounters();
663 CheckRpcSendOk(DEBUG_LOCATION, 6,
664 RpcOptions()
665 .set_metadata({echo2_cookie.front().Header()})
666 .set_rpc_method(METHOD_ECHO2));
667 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 6);
668 // Echo honours the original cookie but not the override cookie
669 backends_[0]->backend_service()->ResetCounters();
670 CheckRpcSendOk(DEBUG_LOCATION, 6,
671 RpcOptions()
672 .set_metadata({echo_cookie.front().Header()})
673 .set_rpc_method(METHOD_ECHO));
674 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 6);
675 backends_[0]->backend_service()->ResetCounters();
676 backends_[1]->backend_service()->ResetCounters();
677 CheckRpcSendOk(DEBUG_LOCATION, 6,
678 RpcOptions()
679 .set_metadata({echo2_cookie.front().Header()})
680 .set_rpc_method(METHOD_ECHO));
681 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 3);
682 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 3);
683 }
684
TEST_P(OverrideHostTest,TTLSetsMaxAge)685 TEST_P(OverrideHostTest, TTLSetsMaxAge) {
686 CreateAndStartBackends(1);
687 RouteConfiguration route_config = default_route_config_;
688 *route_config.mutable_virtual_hosts(0)->mutable_routes(0) =
689 BuildStatefulSessionRouteConfig("", kCookieName,
690 grpc_core::Duration::Seconds(42));
691 SetListenerAndRouteConfiguration(balancer_.get(),
692 BuildListenerWithStatefulSessionFilter(""),
693 route_config);
694 balancer_->ads_service()->SetEdsResource(
695 BuildEdsResource(EdsResourceArgs({{"locality0", {CreateEndpoint(0)}}})));
696 WaitForAllBackends(DEBUG_LOCATION);
697 // Get cookie for backend #0.
698 auto cookies = GetCookiesForBackend(DEBUG_LOCATION, 0);
699 ASSERT_EQ(cookies.size(), 1);
700 EXPECT_THAT(cookies.front().attributes,
701 ::testing::UnorderedElementsAre("Max-Age=42", "HttpOnly"));
702 }
703
TEST_P(OverrideHostTest,MultipleAddressesPerEndpoint)704 TEST_P(OverrideHostTest, MultipleAddressesPerEndpoint) {
705 grpc_core::testing::ScopedExperimentalEnvVar env(
706 "GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS");
707 // Create 3 backends, but leave backend 0 unstarted.
708 CreateBackends(3);
709 StartBackend(1);
710 StartBackend(2);
711 SetListenerAndRouteConfiguration(balancer_.get(),
712 BuildListenerWithStatefulSessionFilter(),
713 default_route_config_);
714 balancer_->ads_service()->SetEdsResource(BuildEdsResource(
715 EdsResourceArgs({{"locality0",
716 {CreateEndpoint(0, HealthStatus::HEALTHY, 1, {1}),
717 CreateEndpoint(2, HealthStatus::UNKNOWN)}}})));
718 WaitForAllBackends(DEBUG_LOCATION, 1); // Wait for backends 1 and 2.
719 // Requests without a cookie should get round-robin across backends 1 and 2.
720 CheckRpcSendOk(DEBUG_LOCATION, 10);
721 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 0);
722 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
723 EXPECT_EQ(backends_[2]->backend_service()->request_count(), 5);
724 ResetBackendCounters();
725 // Get cookie for backend 1.
726 auto cookies = GetCookiesForBackend(DEBUG_LOCATION, 1);
727 ASSERT_THAT(cookies,
728 ::testing::ElementsAre(::testing::AllOf(
729 ::testing::Field("name", &Cookie::name, kCookieName),
730 ::testing::Field("attributes", &Cookie::attributes,
731 ::testing::ElementsAre("HttpOnly")),
732 ::testing::Field("value", &Cookie::value,
733 ::testing::Not(::testing::IsEmpty())))));
734 // Requests with the cookie always go to the same backend.
735 CheckRpcSendOk(DEBUG_LOCATION, 5,
736 RpcOptions().set_metadata({cookies.front().Header()}));
737 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 5);
738 // Now start backend 0 and stop backend 1.
739 StartBackend(0);
740 ShutdownBackend(1);
741 // Wait for traffic to go to backend 0.
742 WaitForBackend(DEBUG_LOCATION, 0);
743 // Requests with no cookie should get round-robin across backends 0 and 2.
744 CheckRpcSendOk(DEBUG_LOCATION, 10);
745 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
746 EXPECT_EQ(backends_[1]->backend_service()->request_count(), 0);
747 EXPECT_EQ(backends_[2]->backend_service()->request_count(), 5);
748 ResetBackendCounters();
749 // Requests with the same cookie should now go to backend 0.
750 CheckRpcSendOk(DEBUG_LOCATION, 5,
751 RpcOptions().set_metadata({cookies.front().Header()}));
752 EXPECT_EQ(backends_[0]->backend_service()->request_count(), 5);
753 }
754
755 } // namespace
756 } // namespace testing
757 } // namespace grpc
758
main(int argc,char ** argv)759 int main(int argc, char** argv) {
760 grpc::testing::TestEnvironment env(&argc, argv);
761 ::testing::InitGoogleTest(&argc, argv);
762 // Make the backup poller poll very frequently in order to pick up
763 // updates from all the subchannels's FDs.
764 grpc_core::ConfigVars::Overrides overrides;
765 overrides.client_channel_backup_poll_interval_ms = 1;
766 grpc_core::ConfigVars::SetOverrides(overrides);
767 #if TARGET_OS_IPHONE
768 // Workaround Apple CFStream bug
769 grpc_core::SetEnv("grpc_cfstream", "0");
770 #endif
771 grpc_init();
772 const auto result = RUN_ALL_TESTS();
773 grpc_shutdown();
774 return result;
775 }
776