xref: /aosp_15_r20/external/grpc-grpc/test/cpp/end2end/xds/xds_override_host_end2end_test.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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