@ -18,18 +18,23 @@
# include "src/core/ext/filters/client_channel/lb_policy/xds/xds_override_host.h"
# include <stddef.h>
# include <algorithm>
# include <atomic>
# include <functional>
# include <map>
# include <memory>
# include <set>
# include <string>
# include <unordered_set >
# include <tuple >
# include <utility>
# include <vector>
# include "absl/base/thread_annotations.h"
# include "absl/status/status.h"
# include "absl/status/statusor.h"
# include "absl/strings/str_cat.h"
# include "absl/strings/string_view.h"
# include "absl/types/optional.h"
# include "absl/types/variant.h"
@ -41,11 +46,13 @@
# include "src/core/ext/filters/client_channel/lb_call_state_internal.h"
# include "src/core/ext/filters/client_channel/lb_policy/child_policy_handler.h"
# include "src/core/ext/filters/stateful_session/stateful_session_filter.h"
# include "src/core/ext/xds/xds_health_status.h"
# include "src/core/lib/address_utils/sockaddr_utils.h"
# include "src/core/lib/channel/channel_args.h"
# include "src/core/lib/config/core_configuration.h"
# include "src/core/lib/debug/trace.h"
# include "src/core/lib/gprpp/debug_location.h"
# include "src/core/lib/gprpp/match.h"
# include "src/core/lib/gprpp/orphanable.h"
# include "src/core/lib/gprpp/ref_counted_ptr.h"
# include "src/core/lib/gprpp/sync.h"
@ -70,6 +77,31 @@ namespace grpc_core {
TraceFlag grpc_lb_xds_override_host_trace ( false , " xds_override_host_lb " ) ;
namespace {
template < typename Value >
struct PtrLessThan {
using is_transparent = void ;
bool operator ( ) ( const std : : unique_ptr < Value > & v1 ,
const std : : unique_ptr < Value > & v2 ) const {
return v1 < v2 ;
}
bool operator ( ) ( const Value * v1 , const Value * v2 ) const { return v1 < v2 ; }
bool operator ( ) ( const Value * v1 , const std : : unique_ptr < Value > & v2 ) const {
return v1 < v2 . get ( ) ;
}
bool operator ( ) ( const std : : unique_ptr < Value > & v1 , const Value * v2 ) const {
return v1 . get ( ) < v2 ;
}
} ;
XdsHealthStatus GetAddressHealthStatus ( const ServerAddress & address ) {
auto attribute = address . GetAttribute ( XdsEndpointHealthStatusAttribute : : kKey ) ;
if ( attribute = = nullptr ) {
return XdsHealthStatus ( XdsHealthStatus : : HealthStatus : : kUnknown ) ;
}
return static_cast < const XdsEndpointHealthStatusAttribute * > ( attribute )
- > status ( ) ;
}
//
// xds_override_host LB policy
@ -91,7 +123,7 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
public :
SubchannelWrapper ( RefCountedPtr < SubchannelInterface > subchannel ,
RefCountedPtr < XdsOverrideHostLb > policy ,
absl : : optional < const std : : string > key ) ;
absl : : string_view key ) ;
~ SubchannelWrapper ( ) override ;
@ -110,31 +142,32 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
private :
class ConnectivityStateWatcher : public ConnectivityStateWatcherInterface {
public :
ConnectivityStateWatcher (
std : : unique_ptr < ConnectivityStateWatcherInterface > delegate ,
RefCountedPtr < SubchannelWrapper > subchannel )
: delegate_ ( std : : move ( delegate ) ) , subchannel_ ( subchannel ) { }
explicit ConnectivityStateWatcher (
WeakRefCountedPtr < SubchannelWrapper > subchannel )
: subchannel_ ( std : : move ( subchannel ) ) { }
void OnConnectivityStateChange ( grpc_connectivity_state state ,
absl : : Status status ) override {
delegate_ - > OnConnectivityStateChange ( state , status ) ;
subchannel_ - > connectivity_state_ = state ;
}
absl : : Status status ) override ;
grpc_pollset_set * interested_parties ( ) override {
return delegate_ - > interested_parties ( ) ;
}
grpc_pollset_set * interested_parties ( ) override ;
private :
std : : unique_ptr < ConnectivityStateWatcherInterface > delegate_ ;
RefCountedPtr < SubchannelWrapper > subchannel_ ;
WeakRefCountedPtr < SubchannelWrapper > subchannel_ ;
} ;
const absl : : optional < const std : : string > key_ ;
void Orphan ( ) override ;
void UpdateConnectivityState ( grpc_connectivity_state state ,
absl : : Status status ) ;
ConnectivityStateWatcher * watcher_ ;
absl : : optional < std : : string > key_ ;
RefCountedPtr < XdsOverrideHostLb > policy_ ;
std : : atomic < grpc_connectivity_state > connectivity_state_ { GRPC_CHANNEL_IDLE } ;
std : : map < ConnectivityStateWatcherInterface * , ConnectivityStateWatcher * >
std : : set < std : : unique_ptr < ConnectivityStateWatcherInterface > ,
PtrLessThan < ConnectivityStateWatcherInterface > >
watchers_ ;
std : : atomic < grpc_connectivity_state > connectivity_state_ = {
GRPC_CHANNEL_IDLE } ;
} ;
// A picker that wraps the picker from the child for cases when cookie is
@ -142,7 +175,8 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
class Picker : public SubchannelPicker {
public :
Picker ( RefCountedPtr < XdsOverrideHostLb > xds_override_host_lb ,
RefCountedPtr < SubchannelPicker > picker ) ;
RefCountedPtr < SubchannelPicker > picker ,
XdsHealthStatusSet override_host_health_status_set ) ;
PickResult Pick ( PickArgs args ) override ;
@ -159,11 +193,9 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
}
private :
XdsOverrideHostLb * policy ( ) { return subchannel_ - > policy ( ) ; }
static void RunInExecCtx ( void * arg , grpc_error_handle /*error*/ ) {
auto * self = static_cast < SubchannelConnectionRequester * > ( arg ) ;
self - > policy ( ) - > work_serializer ( ) - > Run (
self - > subchannel_ - > policy ( ) - > work_serializer ( ) - > Run (
[ self ] ( ) {
self - > subchannel_ - > RequestConnection ( ) ;
delete self ;
@ -180,6 +212,7 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
RefCountedPtr < XdsOverrideHostLb > policy_ ;
RefCountedPtr < SubchannelPicker > picker_ ;
XdsHealthStatusSet override_host_health_status_set_ ;
} ;
class Helper : public ChannelControlHelper {
@ -207,25 +240,49 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
class SubchannelEntry {
public :
explicit SubchannelEntry ( XdsHealthStatus eds_health_status )
: eds_health_status_ ( eds_health_status ) { }
void SetSubchannel ( SubchannelWrapper * subchannel ) {
if ( eds_health_status_ . status ( ) = = XdsHealthStatus : : kDraining ) {
subchannel_ = subchannel - > Ref ( ) ;
} else {
subchannel_ = subchannel ;
}
void ResetSubchannel ( SubchannelWrapper * expected ) {
if ( subchannel_ = = expected ) {
subchannel_ = nullptr ;
}
void UnsetSubchannel ( ) { subchannel_ = nullptr ; }
SubchannelWrapper * GetSubchannel ( ) const {
return Match (
subchannel_ ,
[ ] ( XdsOverrideHostLb : : SubchannelWrapper * subchannel ) {
return subchannel ;
} ,
[ ] ( RefCountedPtr < XdsOverrideHostLb : : SubchannelWrapper > subchannel ) {
return subchannel . get ( ) ;
} ) ;
}
RefCountedPtr < SubchannelWrapper > GetSubchannel ( ) {
if ( subchannel_ = = nullptr ) {
return nullptr ;
void SetEdsHealthStatus ( XdsHealthStatus eds_health_status ) {
eds_health_status_ = eds_health_status ;
auto subchannel = GetSubchannel ( ) ;
if ( subchannel = = nullptr ) {
return ;
}
return subchannel_ - > Ref ( ) ;
if ( eds_health_status_ . status ( ) = = XdsHealthStatus : : kDraining ) {
subchannel_ = subchannel - > Ref ( ) ;
} else {
subchannel_ = subchannel ;
}
}
XdsHealthStatus eds_health_status ( ) const { return eds_health_status_ ; }
private :
SubchannelWrapper * subchannel_ = nullptr ;
absl : : variant < SubchannelWrapper * , RefCountedPtr < SubchannelWrapper > >
subchannel_ ;
XdsHealthStatus eds_health_status_ ;
} ;
~ XdsOverrideHostLb ( ) override ;
@ -237,17 +294,21 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
void MaybeUpdatePickerLocked ( ) ;
RefCountedPtr < SubchannelWrapper > LookupSubchannel ( absl : : string_view address ) ;
void UpdateAddressMap ( const absl : : StatusOr < ServerAddressList > & addresses ) ;
absl : : StatusOr < ServerAddressList > UpdateAddressMap (
absl : : StatusOr < ServerAddressList > addresses ) ;
RefCountedPtr < SubchannelWrapper > AdoptSubchannel (
ServerAddress address , RefCountedPtr < SubchannelInterface > subchannel ) ;
void Re setSubchannel( absl : : string_view key , SubchannelWrapper * subchannel ) ;
void Un setSubchannel( absl : : string_view key , SubchannelWrapper * subchannel ) ;
RefCountedPtr < SubchannelWrapper > GetSubchannelByAddress (
absl : : string_view address ) ;
absl : : string_view address , XdsHealthStatusSet overriden_health_statuses ) ;
void OnSubchannelConnectivityStateChange ( absl : : string_view subchannel_key )
ABSL_NO_THREAD_SAFETY_ANALYSIS ; // Called from within the worker
// serializer and does not require
// additional synchronization
// Current config from the resolver.
RefCountedPtr < XdsOverrideHostLbConfig > config_ ;
@ -272,8 +333,11 @@ class XdsOverrideHostLb : public LoadBalancingPolicy {
XdsOverrideHostLb : : Picker : : Picker (
RefCountedPtr < XdsOverrideHostLb > xds_override_host_lb ,
RefCountedPtr < SubchannelPicker > picker )
: policy_ ( std : : move ( xds_override_host_lb ) ) , picker_ ( std : : move ( picker ) ) {
RefCountedPtr < SubchannelPicker > picker ,
XdsHealthStatusSet override_host_health_status_set )
: policy_ ( std : : move ( xds_override_host_lb ) ) ,
picker_ ( std : : move ( picker ) ) ,
override_host_health_status_set_ ( override_host_health_status_set ) {
if ( GRPC_TRACE_FLAG_ENABLED ( grpc_lb_xds_override_host_trace ) ) {
gpr_log ( GPR_INFO , " [xds_override_host_lb %p] constructed new picker %p " ,
policy_ . get ( ) , this ) ;
@ -285,7 +349,8 @@ XdsOverrideHostLb::Picker::PickOverridenHost(absl::string_view override_host) {
if ( override_host . length ( ) = = 0 ) {
return absl : : nullopt ;
}
auto subchannel = policy_ - > GetSubchannelByAddress ( override_host ) ;
auto subchannel = policy_ - > GetSubchannelByAddress (
override_host , override_host_health_status_set_ ) ;
if ( subchannel = = nullptr ) {
return absl : : nullopt ;
}
@ -348,6 +413,10 @@ void XdsOverrideHostLb::ShutdownLocked() {
gpr_log ( GPR_INFO , " [xds_override_host_lb %p] shutting down " , this ) ;
}
shutting_down_ = true ;
{
MutexLock lock ( & subchannel_map_mu_ ) ;
subchannel_map_ . clear ( ) ;
}
// Remove the child policy's interested_parties pollset_set from the
// xDS policy.
if ( child_policy_ ! = nullptr ) {
@ -382,10 +451,9 @@ absl::Status XdsOverrideHostLb::UpdateLocked(UpdateArgs args) {
if ( child_policy_ = = nullptr ) {
child_policy_ = CreateChildPolicyLocked ( args . args ) ;
}
UpdateAddressMap ( args . addresses ) ;
// Update child policy.
UpdateArgs update_args ;
update_args . addresses = std : : move ( args . addresses ) ;
update_args . addresses = UpdateAddressMap ( std : : move ( args . addresses ) ) ;
update_args . resolution_note = std : : move ( args . resolution_note ) ;
update_args . config = config_ - > child_config ( ) ;
update_args . args = std : : move ( args . args ) ;
@ -399,7 +467,8 @@ absl::Status XdsOverrideHostLb::UpdateLocked(UpdateArgs args) {
void XdsOverrideHostLb : : MaybeUpdatePickerLocked ( ) {
if ( picker_ ! = nullptr ) {
auto xds_override_host_picker = MakeRefCounted < Picker > ( Ref ( ) , picker_ ) ;
auto xds_override_host_picker = MakeRefCounted < Picker > (
Ref ( ) , picker_ , config_ - > override_host_status_set ( ) ) ;
if ( GRPC_TRACE_FLAG_ENABLED ( grpc_lb_xds_override_host_trace ) ) {
gpr_log ( GPR_INFO ,
" [xds_override_host_lb %p] updating connectivity: state=%s "
@ -435,71 +504,100 @@ OrphanablePtr<LoadBalancingPolicy> XdsOverrideHostLb::CreateChildPolicyLocked(
return lb_policy ;
}
void XdsOverrideHostLb : : UpdateAddressMap (
const absl : : StatusOr < ServerAddressList > & addresses ) {
std : : unordered_set < std : : string > keys ( addresses - > size ( ) ) ;
if ( addresses . ok ( ) ) {
absl : : StatusOr < ServerAddressList > XdsOverrideHostLb : : UpdateAddressMap (
absl : : StatusOr < ServerAddressList > addresses ) {
if ( ! addresses . ok ( ) ) {
return addresses ;
}
ServerAddressList return_value ;
std : : map < const std : : string , XdsHealthStatus > addresses_for_map ;
for ( const auto & address : * addresses ) {
XdsHealthStatus status = GetAddressHealthStatus ( address ) ;
if ( status . status ( ) ! = XdsHealthStatus : : kDraining ) {
return_value . push_back ( address ) ;
} else if ( ! config_ - > override_host_status_set ( ) . Contains ( status ) ) {
// Skip draining hosts if not in the override status set.
continue ;
}
auto key = grpc_sockaddr_to_string ( & address . address ( ) , false ) ;
if ( key . ok ( ) ) {
keys . insert ( std : : move ( * key ) ) ;
}
addresses_for_map . emplace ( std : : move ( * key ) , status ) ;
}
}
{
MutexLock lock ( & subchannel_map_mu_ ) ;
for ( auto it = subchannel_map_ . begin ( ) ; it ! = subchannel_map_ . end ( ) ; ) {
if ( keys . find ( it - > first ) = = keys . end ( ) ) {
if ( addresses_for_map . find ( it - > first ) = = addresses_for_map . end ( ) ) {
it = subchannel_map_ . erase ( it ) ;
} else {
+ + it ;
}
}
for ( const auto & key : keys ) {
if ( subchannel_map_ . find ( key ) = = subchannel_map_ . end ( ) ) {
subchannel_map_ . emplace ( key , SubchannelEntry ( ) ) ;
for ( const auto & key_status : addresses_for_map ) {
auto it = subchannel_map_ . find ( key_status . first ) ;
if ( it = = subchannel_map_ . end ( ) ) {
subchannel_map_ . emplace ( std : : piecewise_construct ,
std : : forward_as_tuple ( key_status . first ) ,
std : : forward_as_tuple ( key_status . second ) ) ;
} else {
it - > second . SetEdsHealthStatus ( key_status . second ) ;
}
}
}
return return_value ;
}
RefCountedPtr < XdsOverrideHostLb : : SubchannelWrapper >
XdsOverrideHostLb : : AdoptSubchannel (
ServerAddress address , RefCountedPtr < SubchannelInterface > subchannel ) {
auto subchannel_key = grpc_sockaddr_to_string ( & address . address ( ) , false ) ;
absl : : optional < std : : string > key ;
if ( subchannel_key . ok ( ) ) {
key = std : : move ( * subchannel_key ) ;
auto key = grpc_sockaddr_to_string ( & address . address ( ) , false ) ;
if ( ! key . ok ( ) ) {
return subchannel ;
}
auto wrapper =
MakeRefCounted < SubchannelWrapper > ( std : : move ( subchannel ) , Ref ( ) , key ) ;
if ( key . has_value ( ) ) {
MakeRefCounted < SubchannelWrapper > ( std : : move ( subchannel ) , Ref ( ) , * key ) ;
MutexLock lock ( & subchannel_map_mu_ ) ;
auto it = subchannel_map_ . find ( * key ) ;
if ( it ! = subchannel_map_ . end ( ) ) {
it - > second . SetSubchannel ( wrapper . get ( ) ) ;
}
}
return wrapper ;
}
void XdsOverrideHostLb : : Re setSubchannel( absl : : string_view key ,
void XdsOverrideHostLb : : Un setSubchannel( absl : : string_view key ,
SubchannelWrapper * subchannel ) {
MutexLock lock ( & subchannel_map_mu_ ) ;
auto it = subchannel_map_ . find ( key ) ;
if ( it ! = subchannel_map_ . end ( ) ) {
it - > second . ResetSubchannel ( subchannel ) ;
if ( subchannel = = it - > second . GetSubchannel ( ) ) {
it - > second . UnsetSubchannel ( ) ;
}
}
}
RefCountedPtr < XdsOverrideHostLb : : SubchannelWrapper >
XdsOverrideHostLb : : GetSubchannelByAddress ( absl : : string_view address ) {
XdsOverrideHostLb : : GetSubchannelByAddress (
absl : : string_view address , XdsHealthStatusSet overriden_health_statuses ) {
MutexLock lock ( & subchannel_map_mu_ ) ;
auto it = subchannel_map_ . find ( address ) ;
if ( it ! = subchannel_map_ . end ( ) ) {
return it - > second . GetSubchannel ( ) ;
if ( it ! = subchannel_map_ . end ( ) & &
overriden_health_statuses . Contains ( it - > second . eds_health_status ( ) ) ) {
return it - > second . GetSubchannel ( ) - > Ref ( ) ;
}
return nullptr ;
}
void XdsOverrideHostLb : : OnSubchannelConnectivityStateChange (
absl : : string_view subchannel_key ) {
auto it = subchannel_map_ . find ( subchannel_key ) ;
if ( it = = subchannel_map_ . end ( ) ) {
return ;
}
if ( it - > second . eds_health_status ( ) . status ( ) = = XdsHealthStatus : : kDraining ) {
MaybeUpdatePickerLocked ( ) ;
}
}
//
// XdsOverrideHostLb::Helper
//
@ -551,37 +649,70 @@ void XdsOverrideHostLb::Helper::AddTraceEvent(TraceSeverity severity,
XdsOverrideHostLb : : SubchannelWrapper : : SubchannelWrapper (
RefCountedPtr < SubchannelInterface > subchannel ,
RefCountedPtr < XdsOverrideHostLb > policy ,
absl : : optional < const std : : string > key )
RefCountedPtr < XdsOverrideHostLb > policy , absl : : string_view key )
: DelegatingSubchannel ( std : : move ( subchannel ) ) ,
key_ ( std : : move ( key ) ) ,
policy_ ( std : : move ( policy ) ) { }
key_ ( key ) ,
policy_ ( std : : move ( policy ) ) {
auto watcher = std : : make_unique < ConnectivityStateWatcher > ( WeakRef ( ) ) ;
watcher_ = watcher . get ( ) ;
wrapped_subchannel ( ) - > WatchConnectivityState ( std : : move ( watcher ) ) ;
}
XdsOverrideHostLb : : SubchannelWrapper : : ~ SubchannelWrapper ( ) {
if ( key_ . has_value ( ) ) {
policy_ - > ResetSubchannel ( * key_ , this ) ;
policy_ - > Un setSubchannel( * key_ , this ) ;
}
}
void XdsOverrideHostLb : : SubchannelWrapper : : WatchConnectivityState (
std : : unique_ptr < ConnectivityStateWatcherInterface > watcher ) {
auto watcher_id = watcher . get ( ) ;
auto wrapper =
std : : make_unique < ConnectivityStateWatcher > ( std : : move ( watcher ) , Ref ( ) ) ;
watchers_ . emplace ( watcher_id , wrapper . get ( ) ) ;
wrapped_subchannel ( ) - > WatchConnectivityState ( std : : move ( wrapper ) ) ;
watchers_ . insert ( std : : move ( watcher ) ) ;
}
void XdsOverrideHostLb : : SubchannelWrapper : : CancelConnectivityStateWatch (
ConnectivityStateWatcherInterface * watcher ) {
auto original_watcher = watchers_ . find ( watcher ) ;
if ( original_watcher ! = watchers_ . end ( ) ) {
wrapped_subchannel ( ) - > CancelConnectivityStateWatch (
original_watcher - > second ) ;
watchers_ . erase ( original_watcher ) ;
auto it = watchers_ . find ( watcher ) ;
if ( it ! = watchers_ . end ( ) ) {
watchers_ . erase ( it ) ;
}
}
void XdsOverrideHostLb : : SubchannelWrapper : : UpdateConnectivityState (
grpc_connectivity_state state , absl : : Status status ) {
connectivity_state_ . store ( state ) ;
// Sending connectivity state notifications to the watchers may cause the set
// of watchers to change, so we can't be iterating over the set of watchers
// while we send the notifications
std : : vector < ConnectivityStateWatcherInterface * > watchers ( watchers_ . size ( ) ) ;
for ( const auto & watcher : watchers_ ) {
watchers . push_back ( watcher . get ( ) ) ;
}
for ( const auto & watcher : watchers ) {
if ( watchers_ . find ( watcher ) ! = watchers_ . end ( ) ) {
watcher - > OnConnectivityStateChange ( state , status ) ;
}
}
if ( key_ . has_value ( ) ) {
policy_ - > OnSubchannelConnectivityStateChange ( * key_ ) ;
}
}
void XdsOverrideHostLb : : SubchannelWrapper : : Orphan ( ) {
key_ . reset ( ) ;
wrapped_subchannel ( ) - > CancelConnectivityStateWatch ( watcher_ ) ;
}
grpc_pollset_set * XdsOverrideHostLb : : SubchannelWrapper : :
ConnectivityStateWatcher : : interested_parties ( ) {
return subchannel_ - > policy_ - > interested_parties ( ) ;
}
void XdsOverrideHostLb : : SubchannelWrapper : : ConnectivityStateWatcher : :
OnConnectivityStateChange ( grpc_connectivity_state state ,
absl : : Status status ) {
subchannel_ - > UpdateConnectivityState ( state , status ) ;
}
//
// factory
//
@ -630,16 +761,18 @@ const JsonLoaderInterface* XdsOverrideHostLbConfig::JsonLoader(
return kJsonLoader ;
}
void XdsOverrideHostLbConfig : : JsonPostLoad ( const Json & json , const JsonArgs & ,
void XdsOverrideHostLbConfig : : JsonPostLoad ( const Json & json ,
const JsonArgs & args ,
ValidationErrors * errors ) {
{
ValidationErrors : : ScopedField field ( errors , " .childPolicy " ) ;
auto it = json . object_value ( ) . find ( " childPolicy " ) ;
if ( it = = json . object_value ( ) . end ( ) ) {
errors - > AddError ( " field not present " ) ;
} else {
auto child_policy_config =
CoreConfiguration : : Get ( ) . lb_policy_registry ( ) . ParseLoadBalancingConfig (
it - > second ) ;
auto child_policy_config = CoreConfiguration : : Get ( )
. lb_policy_registry ( )
. ParseLoadBalancingConfig ( it - > second ) ;
if ( ! child_policy_config . ok ( ) ) {
errors - > AddError ( child_policy_config . status ( ) . message ( ) ) ;
} else {
@ -647,5 +780,29 @@ void XdsOverrideHostLbConfig::JsonPostLoad(const Json& json, const JsonArgs&,
}
}
}
{
ValidationErrors : : ScopedField field ( errors , " .overrideHostStatus " ) ;
auto host_status_list = LoadJsonObjectField < std : : vector < std : : string > > (
json . object_value ( ) , args , " overrideHostStatus " , errors ,
/*required=*/ false ) ;
if ( host_status_list . has_value ( ) ) {
for ( size_t i = 0 ; i < host_status_list - > size ( ) ; + + i ) {
const std : : string & host_status = ( * host_status_list ) [ i ] ;
auto status = XdsHealthStatus : : FromString ( host_status ) ;
if ( ! status . has_value ( ) ) {
ValidationErrors : : ScopedField field ( errors ,
absl : : StrCat ( " [ " , i , " ] " ) ) ;
errors - > AddError ( " invalid host status " ) ;
} else {
override_host_status_set_ . Add ( * status ) ;
}
}
} else {
override_host_status_set_ = XdsHealthStatusSet (
{ XdsHealthStatus ( XdsHealthStatus : : HealthStatus : : kHealthy ) ,
XdsHealthStatus ( XdsHealthStatus : : HealthStatus : : kUnknown ) } ) ;
}
}
}
} // namespace grpc_core