• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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